Вход Регистрация
Файл: concrete5.7.5.6/concrete/vendor/zendframework/zend-cache/src/Storage/Adapter/Filesystem.php
Строк: 1480
<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace ZendCacheStorageAdapter;

use 
Exception as BaseException;
use 
GlobIterator;
use 
stdClass;
use 
ZendCacheException;
use 
ZendCacheStorage;
use 
ZendCacheStorageAvailableSpaceCapableInterface;
use 
ZendCacheStorageCapabilities;
use 
ZendCacheStorageClearByNamespaceInterface;
use 
ZendCacheStorageClearByPrefixInterface;
use 
ZendCacheStorageClearExpiredInterface;
use 
ZendCacheStorageFlushableInterface;
use 
ZendCacheStorageIterableInterface;
use 
ZendCacheStorageOptimizableInterface;
use 
ZendCacheStorageTaggableInterface;
use 
ZendCacheStorageTotalSpaceCapableInterface;
use 
ZendStdlibErrorHandler;

class 
Filesystem extends AbstractAdapter implements
    
AvailableSpaceCapableInterface,
    
ClearByNamespaceInterface,
    
ClearByPrefixInterface,
    
ClearExpiredInterface,
    
FlushableInterface,
    
IterableInterface,
    
OptimizableInterface,
    
TaggableInterface,
    
TotalSpaceCapableInterface
{

    
/**
     * Buffered total space in bytes
     *
     * @var null|int|float
     */
    
protected $totalSpace;

    
/**
     * An identity for the last filespec
     * (cache directory + namespace prefix + key + directory level)
     *
     * @var string
     */
    
protected $lastFileSpecId '';

    
/**
     * The last used filespec
     *
     * @var string
     */
    
protected $lastFileSpec '';

    
/**
     * Set options.
     *
     * @param  array|Traversable|FilesystemOptions $options
     * @return Filesystem
     * @see    getOptions()
     */
    
public function setOptions($options)
    {
        if (!
$options instanceof FilesystemOptions) {
            
$options = new FilesystemOptions($options);
        }

        return 
parent::setOptions($options);
    }

    
/**
     * Get options.
     *
     * @return FilesystemOptions
     * @see setOptions()
     */
    
public function getOptions()
    {
        if (!
$this->options) {
            
$this->setOptions(new FilesystemOptions());
        }
        return 
$this->options;
    }

    
/* FlushableInterface */

    /**
     * Flush the whole storage
     *
     * @throws ExceptionRuntimeException
     * @return bool
     */
    
public function flush()
    {
        
$flags GlobIterator::SKIP_DOTS GlobIterator::CURRENT_AS_PATHNAME;
        
$dir   $this->getOptions()->getCacheDir();
        
$clearFolder null;
        
$clearFolder = function ($dir) use (& $clearFolder$flags) {
            
$it = new GlobIterator($dir DIRECTORY_SEPARATOR '*'$flags);
            foreach (
$it as $pathname) {
                if (
$it->isDir()) {
                    
$clearFolder($pathname);
                    
rmdir($pathname);
                } else {
                    
unlink($pathname);
                }
            }
        };

        
ErrorHandler::start();
        
$clearFolder($dir);
        
$error ErrorHandler::stop();
        if (
$error) {
            throw new 
ExceptionRuntimeException("Flushing directory '{$dir}' failed"0$error);
        }

        return 
true;
    }

    
/* ClearExpiredInterface */

    /**
     * Remove expired items
     *
     * @return bool
     */
    
public function clearExpired()
    {
        
$options   $this->getOptions();
        
$namespace $options->getNamespace();
        
$prefix    = ($namespace === '') ? '' $namespace $options->getNamespaceSeparator();

        
$flags GlobIterator::SKIP_DOTS GlobIterator::CURRENT_AS_FILEINFO;
        
$path  $options->getCacheDir()
            . 
str_repeat(DIRECTORY_SEPARATOR $prefix '*'$options->getDirLevel())
            . 
DIRECTORY_SEPARATOR $prefix '*.dat';
        
$glob = new GlobIterator($path$flags);
        
$time time();
        
$ttl  $options->getTtl();

        
ErrorHandler::start();
        foreach (
$glob as $entry) {
            
$mtime $entry->getMTime();
            if (
$time >= $mtime $ttl) {
                
$pathname $entry->getPathname();
                
unlink($pathname);

                
$tagPathname substr($pathname0, -4) . '.tag';
                if (
file_exists($tagPathname)) {
                    
unlink($tagPathname);
                }
            }
        }
        
$error ErrorHandler::stop();
        if (
$error) {
            throw new 
ExceptionRuntimeException("Failed to clear expired items"0$error);
        }

        return 
true;
    }

    
/* ClearByNamespaceInterface */

    /**
     * Remove items by given namespace
     *
     * @param string $namespace
     * @throws ExceptionRuntimeException
     * @return bool
     */
    
public function clearByNamespace($namespace)
    {
        
$namespace = (string) $namespace;
        if (
$namespace === '') {
            throw new 
ExceptionInvalidArgumentException('No namespace given');
        }

        
$options $this->getOptions();
        
$prefix  $namespace $options->getNamespaceSeparator();

        
$flags GlobIterator::SKIP_DOTS GlobIterator::CURRENT_AS_PATHNAME;
        
$path $options->getCacheDir()
            . 
str_repeat(DIRECTORY_SEPARATOR $prefix '*'$options->getDirLevel())
            . 
DIRECTORY_SEPARATOR $prefix '*';
        
$glob = new GlobIterator($path$flags);

        
ErrorHandler::start();
        foreach (
$glob as $pathname) {
            
unlink($pathname);
        }
        
$error ErrorHandler::stop();
        if (
$error) {
            throw new 
ExceptionRuntimeException("Failed to remove files of '{$path}'"0$error);
        }

        return 
true;
    }

    
/* ClearByPrefixInterface */

    /**
     * Remove items matching given prefix
     *
     * @param string $prefix
     * @throws ExceptionRuntimeException
     * @return bool
     */
    
public function clearByPrefix($prefix)
    {
        
$prefix = (string) $prefix;
        if (
$prefix === '') {
            throw new 
ExceptionInvalidArgumentException('No prefix given');
        }

        
$options   $this->getOptions();
        
$namespace $options->getNamespace();
        
$nsPrefix  = ($namespace === '') ? '' $namespace $options->getNamespaceSeparator();

        
$flags GlobIterator::SKIP_DOTS GlobIterator::CURRENT_AS_PATHNAME;
        
$path $options->getCacheDir()
            . 
str_repeat(DIRECTORY_SEPARATOR $nsPrefix '*'$options->getDirLevel())
            . 
DIRECTORY_SEPARATOR $nsPrefix $prefix '*';
        
$glob = new GlobIterator($path$flags);

        
ErrorHandler::start();
        foreach (
$glob as $pathname) {
            
unlink($pathname);
        }
        
$error ErrorHandler::stop();
        if (
$error) {
            throw new 
ExceptionRuntimeException("Failed to remove files of '{$path}'"0$error);
        }

        return 
true;
    }

    
/* TaggableInterface  */

    /**
     * Set tags to an item by given key.
     * An empty array will remove all tags.
     *
     * @param string   $key
     * @param string[] $tags
     * @return bool
     */
    
public function setTags($key, array $tags)
    {
        
$this->normalizeKey($key);
        if (!
$this->internalHasItem($key)) {
            return 
false;
        }

        
$filespec $this->getFileSpec($key);

        if (!
$tags) {
            
$this->unlink($filespec '.tag');
            return 
true;
        }

        
$this->putFileContent($filespec '.tag'implode("n"$tags));
        return 
true;
    }

    
/**
     * Get tags of an item by given key
     *
     * @param string $key
     * @return string[]|FALSE
     */
    
public function getTags($key)
    {
        
$this->normalizeKey($key);
        if (!
$this->internalHasItem($key)) {
            return 
false;
        }

        
$filespec $this->getFileSpec($key);
        
$tags     = array();
        if (
file_exists($filespec '.tag')) {
            
$tags explode("n"$this->getFileContent($filespec '.tag'));
        }

        return 
$tags;
    }

    
/**
     * Remove items matching given tags.
     *
     * If $disjunction only one of the given tags must match
     * else all given tags must match.
     *
     * @param string[] $tags
     * @param  bool  $disjunction
     * @return bool
     */
    
public function clearByTags(array $tags$disjunction false)
    {
        if (!
$tags) {
            return 
true;
        }

        
$tagCount  count($tags);
        
$options   $this->getOptions();
        
$namespace $options->getNamespace();
        
$prefix    = ($namespace === '') ? '' $namespace $options->getNamespaceSeparator();

        
$flags GlobIterator::SKIP_DOTS GlobIterator::CURRENT_AS_PATHNAME;
        
$path  $options->getCacheDir()
            . 
str_repeat(DIRECTORY_SEPARATOR $prefix '*'$options->getDirLevel())
            . 
DIRECTORY_SEPARATOR $prefix '*.tag';
        
$glob = new GlobIterator($path$flags);

        foreach (
$glob as $pathname) {
            
$diff array_diff($tagsexplode("n"$this->getFileContent($pathname)));

            
$rem  false;
            if (
$disjunction && count($diff) < $tagCount) {
                
$rem true;
            } elseif (!
$disjunction && !$diff) {
                
$rem true;
            }

            if (
$rem) {
                
unlink($pathname);

                
$datPathname substr($pathname0, -4) . '.dat';
                if (
file_exists($datPathname)) {
                    
unlink($datPathname);
                }
            }
        }

        return 
true;
    }

    
/* IterableInterface */

    /**
     * Get the storage iterator
     *
     * @return FilesystemIterator
     */
    
public function getIterator()
    {
        
$options   $this->getOptions();
        
$namespace $options->getNamespace();
        
$prefix    = ($namespace === '') ? '' $namespace $options->getNamespaceSeparator();
        
$path      $options->getCacheDir()
            . 
str_repeat(DIRECTORY_SEPARATOR $prefix '*'$options->getDirLevel())
            . 
DIRECTORY_SEPARATOR $prefix '*.dat';
        return new 
FilesystemIterator($this$path$prefix);
    }

    
/* OptimizableInterface */

    /**
     * Optimize the storage
     *
     * @return bool
     * @return ExceptionRuntimeException
     */
    
public function optimize()
    {
        
$options $this->getOptions();
        if (
$options->getDirLevel()) {
            
$namespace $options->getNamespace();
            
$prefix    = ($namespace === '') ? '' $namespace $options->getNamespaceSeparator();

            
// removes only empty directories
            
$this->rmDir($options->getCacheDir(), $prefix);
        }
        return 
true;
    }

    
/* TotalSpaceCapableInterface */

    /**
     * Get total space in bytes
     *
     * @throws ExceptionRuntimeException
     * @return int|float
     */
    
public function getTotalSpace()
    {
        if (
$this->totalSpace === null) {
            
$path $this->getOptions()->getCacheDir();

            
ErrorHandler::start();
            
$total disk_total_space($path);
            
$error ErrorHandler::stop();
            if (
$total === false) {
                throw new 
ExceptionRuntimeException("Can't detect total space of '{$path}'"0$error);
            }
            
$this->totalSpace $total;

            
// clean total space buffer on change cache_dir
            
$events     $this->getEventManager();
            
$handle     null;
            
$totalSpace = & $this->totalSpace;
            
$callback   = function ($event) use (& $events, & $handle, & $totalSpace) {
                
$params $event->getParams();
                if (isset(
$params['cache_dir'])) {
                    
$totalSpace null;
                    
$events->detach($handle);
                }
            };
            
$events->attach('option'$callback);
        }

        return 
$this->totalSpace;
    }

    
/* AvailableSpaceCapableInterface */

    /**
     * Get available space in bytes
     *
     * @throws ExceptionRuntimeException
     * @return int|float
     */
    
public function getAvailableSpace()
    {
        
$path $this->getOptions()->getCacheDir();

        
ErrorHandler::start();
        
$avail disk_free_space($path);
        
$error ErrorHandler::stop();
        if (
$avail === false) {
            throw new 
ExceptionRuntimeException("Can't detect free space of '{$path}'"0$error);
        }

        return 
$avail;
    }

    
/* reading */

    /**
     * Get an item.
     *
     * @param  string  $key
     * @param  bool $success
     * @param  mixed   $casToken
     * @return mixed Data on success, null on failure
     * @throws ExceptionExceptionInterface
     *
     * @triggers getItem.pre(PreEvent)
     * @triggers getItem.post(PostEvent)
     * @triggers getItem.exception(ExceptionEvent)
     */
    
public function getItem($key, & $success null, & $casToken null)
    {
        
$options $this->getOptions();
        if (
$options->getReadable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        
$argn func_num_args();
        if (
$argn 2) {
            return 
parent::getItem($key$success$casToken);
        } elseif (
$argn 1) {
            return 
parent::getItem($key$success);
        }

        return 
parent::getItem($key);
    }

    
/**
     * Get multiple items.
     *
     * @param  array $keys
     * @return array Associative array of keys and values
     * @throws ExceptionExceptionInterface
     *
     * @triggers getItems.pre(PreEvent)
     * @triggers getItems.post(PostEvent)
     * @triggers getItems.exception(ExceptionEvent)
     */
    
public function getItems(array $keys)
    {
        
$options $this->getOptions();
        if (
$options->getReadable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::getItems($keys);
    }

    
/**
     * Internal method to get an item.
     *
     * @param  string  $normalizedKey
     * @param  bool $success
     * @param  mixed   $casToken
     * @return mixed Data on success, null on failure
     * @throws ExceptionExceptionInterface
     */
    
protected function internalGetItem(& $normalizedKey, & $success null, & $casToken null)
    {
        if (!
$this->internalHasItem($normalizedKey)) {
            
$success false;
            return 
null;
        }

        try {
            
$filespec $this->getFileSpec($normalizedKey);
            
$data     $this->getFileContent($filespec '.dat');

            
// use filemtime + filesize as CAS token
            
if (func_num_args() > 2) {
                
$casToken filemtime($filespec '.dat') . filesize($filespec '.dat');
            }
            
$success  true;
            return 
$data;

        } catch (
BaseException $e) {
            
$success false;
            throw 
$e;
        }
    }

    
/**
     * Internal method to get multiple items.
     *
     * @param  array $normalizedKeys
     * @return array Associative array of keys and values
     * @throws ExceptionExceptionInterface
     */
    
protected function internalGetItems(array & $normalizedKeys)
    {
        
$keys    $normalizedKeys// Don't change argument passed by reference
        
$result  = array();
        while (
$keys) {

            
// LOCK_NB if more than one items have to read
            
$nonBlocking count($keys) > 1;
            
$wouldblock  null;

            
// read items
            
foreach ($keys as $i => $key) {
                if (!
$this->internalHasItem($key)) {
                    unset(
$keys[$i]);
                    continue;
                }

                
$filespec $this->getFileSpec($key);
                
$data     $this->getFileContent($filespec '.dat'$nonBlocking$wouldblock);
                if (
$nonBlocking && $wouldblock) {
                    continue;
                } else {
                    unset(
$keys[$i]);
                }

                
$result[$key] = $data;
            }

            
// TODO: Don't check ttl after first iteration
            // $options['ttl'] = 0;
        
}

        return 
$result;
    }

    
/**
     * Test if an item exists.
     *
     * @param  string $key
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers hasItem.pre(PreEvent)
     * @triggers hasItem.post(PostEvent)
     * @triggers hasItem.exception(ExceptionEvent)
     */
    
public function hasItem($key)
    {
        
$options $this->getOptions();
        if (
$options->getReadable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::hasItem($key);
    }

    
/**
     * Test multiple items.
     *
     * @param  array $keys
     * @return array Array of found keys
     * @throws ExceptionExceptionInterface
     *
     * @triggers hasItems.pre(PreEvent)
     * @triggers hasItems.post(PostEvent)
     * @triggers hasItems.exception(ExceptionEvent)
     */
    
public function hasItems(array $keys)
    {
        
$options $this->getOptions();
        if (
$options->getReadable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::hasItems($keys);
    }

    
/**
     * Internal method to test if an item exists.
     *
     * @param  string $normalizedKey
     * @return bool
     * @throws ExceptionExceptionInterface
     */
    
protected function internalHasItem(& $normalizedKey)
    {
        
$file $this->getFileSpec($normalizedKey) . '.dat';
        if (!
file_exists($file)) {
            return 
false;
        }

        
$ttl $this->getOptions()->getTtl();
        if (
$ttl) {
            
ErrorHandler::start();
            
$mtime filemtime($file);
            
$error ErrorHandler::stop();
            if (!
$mtime) {
                throw new 
ExceptionRuntimeException(
                    
"Error getting mtime of file '{$file}'"0$error
                
);
            }

            if (
time() >= ($mtime $ttl)) {
                return 
false;
            }
        }

        return 
true;
    }

    
/**
     * Get metadata
     *
     * @param string $key
     * @return array|bool Metadata on success, false on failure
     */
    
public function getMetadata($key)
    {
        
$options $this->getOptions();
        if (
$options->getReadable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::getMetadata($key);
    }

    
/**
     * Get metadatas
     *
     * @param array $keys
     * @param array $options
     * @return array Associative array of keys and metadata
     */
    
public function getMetadatas(array $keys, array $options = array())
    {
        
$options $this->getOptions();
        if (
$options->getReadable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::getMetadatas($keys);
    }

    
/**
     * Get info by key
     *
     * @param string $normalizedKey
     * @return array|bool Metadata on success, false on failure
     */
    
protected function internalGetMetadata(& $normalizedKey)
    {
        if (!
$this->internalHasItem($normalizedKey)) {
            return 
false;
        }

        
$options  $this->getOptions();
        
$filespec $this->getFileSpec($normalizedKey);
        
$file     $filespec '.dat';

        
$metadata = array(
            
'filespec' => $filespec,
            
'mtime'    => filemtime($file)
        );

        if (!
$options->getNoCtime()) {
            
$metadata['ctime'] = filectime($file);
        }

        if (!
$options->getNoAtime()) {
            
$metadata['atime'] = fileatime($file);
        }

        return 
$metadata;
    }

    
/**
     * Internal method to get multiple metadata
     *
     * @param  array $normalizedKeys
     * @return array Associative array of keys and metadata
     * @throws ExceptionExceptionInterface
     */
    
protected function internalGetMetadatas(array & $normalizedKeys)
    {
        
$options $this->getOptions();
        
$result  = array();

        foreach (
$normalizedKeys as $normalizedKey) {
            
$filespec $this->getFileSpec($normalizedKey);
            
$file     $filespec '.dat';

            
$metadata = array(
                
'filespec' => $filespec,
                
'mtime'    => filemtime($file),
            );

            if (!
$options->getNoCtime()) {
                
$metadata['ctime'] = filectime($file);
            }

            if (!
$options->getNoAtime()) {
                
$metadata['atime'] = fileatime($file);
            }

            
$result[$normalizedKey] = $metadata;
        }

        return 
$result;
    }

    
/* writing */

    /**
     * Store an item.
     *
     * @param  string $key
     * @param  mixed  $value
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers setItem.pre(PreEvent)
     * @triggers setItem.post(PostEvent)
     * @triggers setItem.exception(ExceptionEvent)
     */
    
public function setItem($key$value)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }
        return 
parent::setItem($key$value);
    }

    
/**
     * Store multiple items.
     *
     * @param  array $keyValuePairs
     * @return array Array of not stored keys
     * @throws ExceptionExceptionInterface
     *
     * @triggers setItems.pre(PreEvent)
     * @triggers setItems.post(PostEvent)
     * @triggers setItems.exception(ExceptionEvent)
     */
    
public function setItems(array $keyValuePairs)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::setItems($keyValuePairs);
    }

    
/**
     * Add an item.
     *
     * @param  string $key
     * @param  mixed  $value
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers addItem.pre(PreEvent)
     * @triggers addItem.post(PostEvent)
     * @triggers addItem.exception(ExceptionEvent)
     */
    
public function addItem($key$value)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::addItem($key$value);
    }

    
/**
     * Add multiple items.
     *
     * @param  array $keyValuePairs
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers addItems.pre(PreEvent)
     * @triggers addItems.post(PostEvent)
     * @triggers addItems.exception(ExceptionEvent)
     */
    
public function addItems(array $keyValuePairs)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::addItems($keyValuePairs);
    }

    
/**
     * Replace an existing item.
     *
     * @param  string $key
     * @param  mixed  $value
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers replaceItem.pre(PreEvent)
     * @triggers replaceItem.post(PostEvent)
     * @triggers replaceItem.exception(ExceptionEvent)
     */
    
public function replaceItem($key$value)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::replaceItem($key$value);
    }

    
/**
     * Replace multiple existing items.
     *
     * @param  array $keyValuePairs
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers replaceItems.pre(PreEvent)
     * @triggers replaceItems.post(PostEvent)
     * @triggers replaceItems.exception(ExceptionEvent)
     */
    
public function replaceItems(array $keyValuePairs)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::replaceItems($keyValuePairs);
    }

    
/**
     * Internal method to store an item.
     *
     * @param  string $normalizedKey
     * @param  mixed  $value
     * @return bool
     * @throws ExceptionExceptionInterface
     */
    
protected function internalSetItem(& $normalizedKey, & $value)
    {
        
$filespec $this->getFileSpec($normalizedKey);
        
$this->prepareDirectoryStructure($filespec);

        
// write data in non-blocking mode
        
$wouldblock null;
        
$this->putFileContent($filespec '.dat'$valuetrue$wouldblock);

        
// delete related tag file (if present)
        
$this->unlink($filespec '.tag');

        
// Retry writing data in blocking mode if it was blocked before
        
if ($wouldblock) {
            
$this->putFileContent($filespec '.dat'$value);
        }

        return 
true;
    }

    
/**
     * Internal method to store multiple items.
     *
     * @param  array $normalizedKeyValuePairs
     * @return array Array of not stored keys
     * @throws ExceptionExceptionInterface
     */
    
protected function internalSetItems(array & $normalizedKeyValuePairs)
    {
        
$oldUmask    null;

        
// create an associated array of files and contents to write
        
$contents = array();
        foreach (
$normalizedKeyValuePairs as $key => & $value) {
            
$filespec $this->getFileSpec($key);
            
$this->prepareDirectoryStructure($filespec);

            
// *.dat file
            
$contents[$filespec '.dat'] = & $value;

            
// *.tag file
            
$this->unlink($filespec '.tag');
        }

        
// write to disk
        
while ($contents) {
            
$nonBlocking count($contents) > 1;
            
$wouldblock  null;

            foreach (
$contents as $file => & $content) {
                
$this->putFileContent($file$content$nonBlocking$wouldblock);
                if (!
$nonBlocking || !$wouldblock) {
                    unset(
$contents[$file]);
                }
            }
        }

        
// return OK
        
return array();
    }

    
/**
     * Set an item only if token matches
     *
     * It uses the token received from getItem() to check if the item has
     * changed before overwriting it.
     *
     * @param  mixed  $token
     * @param  string $key
     * @param  mixed  $value
     * @return bool
     * @throws ExceptionExceptionInterface
     * @see    getItem()
     * @see    setItem()
     */
    
public function checkAndSetItem($token$key$value)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::checkAndSetItem($token$key$value);
    }

    
/**
     * Internal method to set an item only if token matches
     *
     * @param  mixed  $token
     * @param  string $normalizedKey
     * @param  mixed  $value
     * @return bool
     * @throws ExceptionExceptionInterface
     * @see    getItem()
     * @see    setItem()
     */
    
protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value)
    {
        if (!
$this->internalHasItem($normalizedKey)) {
            return 
false;
        }

        
// use filemtime + filesize as CAS token
        
$file  $this->getFileSpec($normalizedKey) . '.dat';
        
$check filemtime($file) . filesize($file);
        if (
$token !== $check) {
            return 
false;
        }

        return 
$this->internalSetItem($normalizedKey$value);
    }

    
/**
     * Reset lifetime of an item
     *
     * @param  string $key
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers touchItem.pre(PreEvent)
     * @triggers touchItem.post(PostEvent)
     * @triggers touchItem.exception(ExceptionEvent)
     */
    
public function touchItem($key)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::touchItem($key);
    }

    
/**
     * Reset lifetime of multiple items.
     *
     * @param  array $keys
     * @return array Array of not updated keys
     * @throws ExceptionExceptionInterface
     *
     * @triggers touchItems.pre(PreEvent)
     * @triggers touchItems.post(PostEvent)
     * @triggers touchItems.exception(ExceptionEvent)
     */
    
public function touchItems(array $keys)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::touchItems($keys);
    }

    
/**
     * Internal method to reset lifetime of an item
     *
     * @param  string $normalizedKey
     * @return bool
     * @throws ExceptionExceptionInterface
     */
    
protected function internalTouchItem(& $normalizedKey)
    {
        if (!
$this->internalHasItem($normalizedKey)) {
            return 
false;
        }

        
$filespec $this->getFileSpec($normalizedKey);

        
ErrorHandler::start();
        
$touch touch($filespec '.dat');
        
$error ErrorHandler::stop();
        if (!
$touch) {
            throw new 
ExceptionRuntimeException(
                
"Error touching file '{$filespec}.dat'"0$error
            
);
        }

        return 
true;
    }

    
/**
     * Remove an item.
     *
     * @param  string $key
     * @return bool
     * @throws ExceptionExceptionInterface
     *
     * @triggers removeItem.pre(PreEvent)
     * @triggers removeItem.post(PostEvent)
     * @triggers removeItem.exception(ExceptionEvent)
     */
    
public function removeItem($key)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::removeItem($key);
    }

    
/**
     * Remove multiple items.
     *
     * @param  array $keys
     * @return array Array of not removed keys
     * @throws ExceptionExceptionInterface
     *
     * @triggers removeItems.pre(PreEvent)
     * @triggers removeItems.post(PostEvent)
     * @triggers removeItems.exception(ExceptionEvent)
     */
    
public function removeItems(array $keys)
    {
        
$options $this->getOptions();
        if (
$options->getWritable() && $options->getClearStatCache()) {
            
clearstatcache();
        }

        return 
parent::removeItems($keys);
    }

    
/**
     * Internal method to remove an item.
     *
     * @param  string $normalizedKey
     * @return bool
     * @throws ExceptionExceptionInterface
     */
    
protected function internalRemoveItem(& $normalizedKey)
    {
        
$filespec $this->getFileSpec($normalizedKey);
        if (!
file_exists($filespec '.dat')) {
            return 
false;
        } else {
            
$this->unlink($filespec '.dat');
            
$this->unlink($filespec '.tag');
        }
        return 
true;
    }

    
/* status */

    /**
     * Internal method to get capabilities of this adapter
     *
     * @return Capabilities
     */
    
protected function internalGetCapabilities()
    {
        if (
$this->capabilities === null) {
            
$marker  = new stdClass();
            
$options $this->getOptions();

            
// detect metadata
            
$metadata = array('mtime''filespec');
            if (!
$options->getNoAtime()) {
                
$metadata[] = 'atime';
            }
            if (!
$options->getNoCtime()) {
                
$metadata[] = 'ctime';
            }

            
$capabilities = new Capabilities(
                
$this,
                
$marker,
                array(
                    
'supportedDatatypes' => array(
                        
'NULL'     => 'string',
                        
'boolean'  => 'string',
                        
'integer'  => 'string',
                        
'double'   => 'string',
                        
'string'   => true,
                        
'array'    => false,
                        
'object'   => false,
                        
'resource' => false,
                    ),
                    
'supportedMetadata'  => $metadata,
                    
'minTtl'             => 1,
                    
'maxTtl'             => 0,
                    
'staticTtl'          => false,
                    
'ttlPrecision'       => 1,
                    
'expiredRead'        => true,
                    
'maxKeyLength'       => 251// 255 - strlen(.dat | .tag)
                    
'namespaceIsPrefix'  => true,
                    
'namespaceSeparator' => $options->getNamespaceSeparator(),
                )
            );

            
// update capabilities on change options
            
$this->getEventManager()->attach('option', function ($event) use ($capabilities$marker) {
                
$params $event->getParams();

                if (isset(
$params['namespace_separator'])) {
                    
$capabilities->setNamespaceSeparator($marker$params['namespace_separator']);
                }

                if (isset(
$params['no_atime']) || isset($params['no_ctime'])) {
                    
$metadata $capabilities->getSupportedMetadata();

                    if (isset(
$params['no_atime']) && !$params['no_atime']) {
                        
$metadata[] = 'atime';
                    } elseif (isset(
$params['no_atime']) && ($index array_search('atime'$metadata)) !== false) {
                        unset(
$metadata[$index]);
                    }

                    if (isset(
$params['no_ctime']) && !$params['no_ctime']) {
                        
$metadata[] = 'ctime';
                    } elseif (isset(
$params['no_ctime']) && ($index array_search('ctime'$metadata)) !== false) {
                        unset(
$metadata[$index]);
                    }

                    
$capabilities->setSupportedMetadata($marker$metadata);
                }
            });

            
$this->capabilityMarker $marker;
            
$this->capabilities     $capabilities;
        }

        return 
$this->capabilities;
    }

    
/* internal */

    /**
     * Removes directories recursive by namespace
     *
     * @param  string $dir    Directory to delete
     * @param  string $prefix Namespace + Separator
     * @return bool
     */
    
protected function rmDir($dir$prefix)
    {
        
$glob glob(
            
$dir DIRECTORY_SEPARATOR $prefix  '*',
            
GLOB_ONLYDIR GLOB_NOESCAPE GLOB_NOSORT
        
);
        if (!
$glob) {
            
// On some systems glob returns false even on empty result
            
return true;
        }

        
$ret true;
        foreach (
$glob as $subdir) {
            
// skip removing current directory if removing of sub-directory failed
            
if ($this->rmDir($subdir$prefix)) {
                
// ignore not empty directories
                
ErrorHandler::start();
                
$ret rmdir($subdir) && $ret;
                
ErrorHandler::stop();
            } else {
                
$ret false;
            }
        }

        return 
$ret;
    }

    
/**
     * Get file spec of the given key and namespace
     *
     * @param  string $normalizedKey
     * @return string
     */
    
protected function getFileSpec($normalizedKey)
    {
        
$options   $this->getOptions();
        
$namespace $options->getNamespace();
        
$prefix    = ($namespace === '') ? '' $namespace $options->getNamespaceSeparator();
        
$path      $options->getCacheDir() . DIRECTORY_SEPARATOR;
        
$level     $options->getDirLevel();

        
$fileSpecId $path $prefix $normalizedKey '/' $level;
        if (
$this->lastFileSpecId !== $fileSpecId) {
            if (
$level 0) {
                
// create up to 256 directories per directory level
                
$hash md5($normalizedKey);
                for (
$i 0$max = ($level 2); $i $max$i+= 2) {
                    
$path .= $prefix $hash[$i] . $hash[$i+1] . DIRECTORY_SEPARATOR;
                }
            }

            
$this->lastFileSpecId $fileSpecId;
            
$this->lastFileSpec   $path $prefix $normalizedKey;
        }

        return 
$this->lastFileSpec;
    }

    
/**
     * Read info file
     *
     * @param  string  $file
     * @param  bool $nonBlocking Don't block script if file is locked
     * @param  bool $wouldblock  The optional argument is set to TRUE if the lock would block
     * @return array|bool The info array or false if file wasn't found
     * @throws ExceptionRuntimeException
     */
    
protected function readInfoFile($file$nonBlocking false, & $wouldblock null)
    {
        if (!
file_exists($file)) {
            return 
false;
        }

        
$content $this->getFileContent($file$nonBlocking$wouldblock);
        if (
$nonBlocking && $wouldblock) {
            return 
false;
        }

        
ErrorHandler::start();
        
$ifo unserialize($content);
        
$err ErrorHandler::stop();
        if (!
is_array($ifo)) {
            throw new 
ExceptionRuntimeException(
                
"Corrupted info file '{$file}'"0$err
            
);
        }

        return 
$ifo;
    }

    
/**
     * Read a complete file
     *
     * @param  string  $file        File complete path
     * @param  bool $nonBlocking Don't block script if file is locked
     * @param  bool $wouldblock  The optional argument is set to TRUE if the lock would block
     * @return string
     * @throws ExceptionRuntimeException
     */
    
protected function getFileContent($file$nonBlocking false, & $wouldblock null)
    {
        
$locking    $this->getOptions()->getFileLocking();
        
$wouldblock null;

        
ErrorHandler::start();

        
// if file locking enabled -> file_get_contents can't be used
        
if ($locking) {
            
$fp fopen($file'rb');
            if (
$fp === false) {
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"Error opening file '{$file}'"0$err
                
);
            }

            if (
$nonBlocking) {
                
$lock flock($fpLOCK_SH LOCK_NB$wouldblock);
                if (
$wouldblock) {
                    
fclose($fp);
                    
ErrorHandler::stop();
                    return;
                }
            } else {
                
$lock flock($fpLOCK_SH);
            }

            if (!
$lock) {
                
fclose($fp);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"Error locking file '{$file}'"0$err
                
);
            }

            
$res stream_get_contents($fp);
            if (
$res === false) {
                
flock($fpLOCK_UN);
                
fclose($fp);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
'Error getting stream contents'0$err
                
);
            }

            
flock($fpLOCK_UN);
            
fclose($fp);

        
// if file locking disabled -> file_get_contents can be used
        
} else {
            
$res file_get_contents($filefalse);
            if (
$res === false) {
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"Error getting file contents for file '{$file}'"0$err
                
);
            }
        }

        
ErrorHandler::stop();
        return 
$res;
    }

    
/**
     * Prepares a directory structure for the given file(spec)
     * using the configured directory level.
     *
     * @param string $file
     * @return void
     * @throws ExceptionRuntimeException
     */
    
protected function prepareDirectoryStructure($file)
    {
        
$options $this->getOptions();
        
$level   $options->getDirLevel();

        
// Directory structure is required only if directory level > 0
        
if (!$level) {
            return;
        }

        
// Directory structure already exists
        
$pathname dirname($file);
        if (
file_exists($pathname)) {
            return;
        }

        
$perm     $options->getDirPermission();
        
$umask    $options->getUmask();
        if (
$umask !== false && $perm !== false) {
            
$perm $perm & ~$umask;
        }

        
ErrorHandler::start();

        if (
$perm === false || $level == 1) {
            
// build-in mkdir function is enough

            
$umask = ($umask !== false) ? umask($umask) : false;
            
$res   mkdir($pathname, ($perm !== false) ? $perm 0777true);

            if (
$umask !== false) {
                
umask($umask);
            }

            if (!
$res) {
                
$oct = ($perm === false) ? '777' decoct($perm);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"mkdir('{$pathname}', 0{$oct}, true) failed"0$err
                
);
            }

            if (
$perm !== false && !chmod($pathname$perm)) {
                
$oct decoct($perm);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"chmod('{$pathname}', 0{$oct}) failed"0$err
                
);
            }

        } else {
            
// build-in mkdir function sets permission together with current umask
            // which doesn't work well on multo threaded webservers
            // -> create directories one by one and set permissions

            // find existing path and missing path parts
            
$parts = array();
            
$path  $pathname;
            while (!
file_exists($path)) {
                
array_unshift($partsbasename($path));
                
$nextPath dirname($path);
                if (
$nextPath === $path) {
                    break;
                }
                
$path $nextPath;
            }

            
// make all missing path parts
            
foreach ($parts as $part) {
                
$path.= DIRECTORY_SEPARATOR $part;

                
// create a single directory, set and reset umask immediately
                
$umask = ($umask !== false) ? umask($umask) : false;
                
$res   mkdir($path, ($perm === false) ? 0777 $permfalse);
                if (
$umask !== false) {
                    
umask($umask);
                }

                if (!
$res) {
                    
$oct = ($perm === false) ? '777' decoct($perm);
                    
ErrorHandler::stop();
                    throw new 
ExceptionRuntimeException(
                        
"mkdir('{$path}', 0{$oct}, false) failed"
                    
);
                }

                if (
$perm !== false && !chmod($path$perm)) {
                    
$oct decoct($perm);
                    
ErrorHandler::stop();
                    throw new 
ExceptionRuntimeException(
                        
"chmod('{$path}', 0{$oct}) failed"
                    
);
                }
            }
        }

        
ErrorHandler::stop();
    }

    
/**
     * Write content to a file
     *
     * @param  string  $file        File complete path
     * @param  string  $data        Data to write
     * @param  bool $nonBlocking Don't block script if file is locked
     * @param  bool $wouldblock  The optional argument is set to TRUE if the lock would block
     * @return void
     * @throws ExceptionRuntimeException
     */
    
protected function putFileContent($file$data$nonBlocking false, & $wouldblock null)
    {
        
$options     $this->getOptions();
        
$locking     $options->getFileLocking();
        
$nonBlocking $locking && $nonBlocking;
        
$wouldblock  null;

        
$umask $options->getUmask();
        
$perm  $options->getFilePermission();
        if (
$umask !== false && $perm !== false) {
            
$perm $perm & ~$umask;
        }

        
ErrorHandler::start();

        
// if locking and non blocking is enabled -> file_put_contents can't used
        
if ($locking && $nonBlocking) {

            
$umask = ($umask !== false) ? umask($umask) : false;

            
$fp fopen($file'cb');

            if (
$umask) {
                
umask($umask);
            }

            if (!
$fp) {
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"Error opening file '{$file}'"0$err
                
);
            }

            if (
$perm !== false && !chmod($file$perm)) {
                
fclose($fp);
                
$oct decoct($perm);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException("chmod('{$file}', 0{$oct}) failed"0$err);
            }

            if (!
flock($fpLOCK_EX LOCK_NB$wouldblock)) {
                
fclose($fp);
                
$err ErrorHandler::stop();
                if (
$wouldblock) {
                    return;
                } else {
                    throw new 
ExceptionRuntimeException("Error locking file '{$file}'"0$err);
                }
            }

            if (
fwrite($fp$data) === false) {
                
flock($fpLOCK_UN);
                
fclose($fp);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException("Error writing file '{$file}'"0$err);
            }

            if (!
ftruncate($fpstrlen($data))) {
                
flock($fpLOCK_UN);
                
fclose($fp);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException("Error truncating file '{$file}'"0$err);
            }

            
flock($fpLOCK_UN);
            
fclose($fp);

        
// else -> file_put_contents can be used
        
} else {
            
$flags 0;
            if (
$locking) {
                
$flags $flags LOCK_EX;
            }

            
$umask = ($umask !== false) ? umask($umask) : false;

            
$rs file_put_contents($file$data$flags);

            if (
$umask) {
                
umask($umask);
            }

            if (
$rs === false) {
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException(
                    
"Error writing file '{$file}'"0$err
                
);
            }

            if (
$perm !== false && !chmod($file$perm)) {
                
$oct decoct($perm);
                
$err ErrorHandler::stop();
                throw new 
ExceptionRuntimeException("chmod('{$file}', 0{$oct}) failed"0$err);
            }
        }

        
ErrorHandler::stop();
    }

    
/**
     * Unlink a file
     *
     * @param string $file
     * @return void
     * @throws RuntimeException
     */
    
protected function unlink($file)
    {
        
ErrorHandler::start();
        
$res unlink($file);
        
$err ErrorHandler::stop();

        
// only throw exception if file still exists after deleting
        
if (!$res && file_exists($file)) {
            throw new 
ExceptionRuntimeException(
                
"Error unlinking file '{$file}'; file still exists"0$err
            
);
        }
    }
}
Онлайн: 0
Реклама