blob: e73096938d7a0091a4d769a3b76f45de77aea513 [file] [log] [blame]
<?php
/**
* File containing the ezcCacheStorageFileApcArray class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Cache
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @filesource
*/
/**
* This class is a replacement for the {@link ezcCacheStorageFileArray} class. Tries
* to serve data from a local APC cache if possible.
*
* Options for this class are defined in {@link ezcCacheStorageFileApcArrayOptions}.
*
* @apichange This class might be removed in future versions. Please use
* {@link ezcCacheStack} to achieve the desired behaior.
* @package Cache
* @version //autogentag//
*/
class ezcCacheStorageFileApcArray extends ezcCacheStorageApc
{
/**
* Creates a new cache storage in the given location. The location in case
* of this storage class must a valid file system directory.
*
* Options can contain the 'ttl' (Time-To-Live). This is per default set
* to 1 day. The option 'permissions' can be used to define the file
* permissions of created cache items.
*
* For details about the options see {@link ezcCacheStorageFileApcArrayOptions}.
*
* @throws ezcBasePropertyNotFoundException
* If you tried to set a non-existent option value.
*
* @param string $location Path to the cache location. Must be a valid path
* @param array(string=>string) $options Options for the cache storage
*/
public function __construct( $location, array $options = array() )
{
parent::__construct( $location, array() );
// Overwrite parent set options with new ezcCacheStorageFileApcArrayOptions
$this->properties['options'] = new ezcCacheStorageFileApcArrayOptions( $options );
}
/**
* Fetches the data from the cache.
*
* @param string $filename The ID/filename from where to fetch the object
* @param bool $useApc Use APC or the file system
* @return mixed The fetched data or false on failure
*/
protected function fetchData( $filename, $useApc = false )
{
if ( $useApc === true )
{
$data = $this->backend->fetch( $filename );
return ( is_object( $data ) ) ? $data->data : false;
}
else
{
return ( include $filename );
}
}
/**
* Fetches the object from the cache.
*
* @param string $filename The ID/filename from where to fetch the data
* @return mixed The fetched object or false on failure
*/
protected function fetchObject( $filename )
{
$data = $this->backend->fetch( $filename );
return ( is_object( $data ) ) ? $data : false;
}
/**
* Wraps the data in order to be stored in APC ($useApc = true) or on the
* file system ($useApc = false).
*
* @throws ezcCacheInvalidDataException
* If the data submitted can not be handled by this storage (object,
* resource).
*
* @param mixed $data Simple type or array
* @param bool $useApc Use APC or not
* @return mixed Prepared data
*/
protected function prepareData( $data, $useApc = false )
{
if ( $useApc === true )
{
if ( is_resource( $data ) )
{
throw new ezcCacheInvalidDataException( gettype( $data ), array( 'simple', 'array', 'object' ) );
}
return new ezcCacheStorageFileApcArrayDataStruct( $data, $this->properties['location'] );
}
else
{
if ( is_object( $data )
|| is_resource( $data ) )
{
throw new ezcCacheInvalidDataException( gettype( $data ), array( 'simple', 'array' ) );
}
return "<?php\nreturn " . var_export( $data, true ) . ";\n?>\n";
}
}
/**
* Stores data to the cache storage.
*
* @throws ezcBaseFilePermissionException
* If the directory to store the cache file could not be created.
* This exception means most likely that your cache directory
* has been corrupted by external influences (file permission
* change).
* @throws ezcBaseFileIoException
* If an error occured while writing the data to the cache. If this
* exception occurs, a serious error occured and your storage might
* be corruped (e.g. broken network connection, file system broken,
* ...).
* @throws ezcCacheInvalidDataException
* If the data submitted can not be handled by the implementation
* of {@link ezcCacheStorageFile}. Most implementations can not
* handle objects and resources.
* @throws ezcCacheApcException
* If the data could not be stored in APC.
*
* @param string $id Unique identifier
* @param mixed $data The data to store
* @param array(string=>string) $attributes Attributes describing the cached data
* @return string The ID string of the newly cached data
*/
public function store( $id, $data, $attributes = array() )
{
// Generates the identifier
$filename = $this->properties['location'] . $this->generateIdentifier( $id, $attributes );
// Purges the Registry Cache
if ( isset( $this->registry[$filename] ) )
{
unset( $this->registry[$filename] );
}
// Deletes the files if it already exists on the filesystem
if ( file_exists( $filename ) )
{
if ( unlink( $filename ) === false )
{
throw new ezcBaseFilePermissionException( $filename, ezcBaseFileException::WRITE, 'Could not delete existing cache file.' );
}
}
// Deletes the data from APC if it already exists
$this->backend->delete( $filename );
// Prepares the data for filesystem storage
$dataStr = $this->prepareData( $data );
// Tries to create the directory on the filesystem
$dirname = dirname( $filename );
if ( !is_dir( $dirname )
&& !mkdir( $dirname, 0777, true ) )
{
throw new ezcBaseFilePermissionException( $dirname, ezcBaseFileException::WRITE, 'Could not create directory to store cache file.' );
}
// Tries to write the file the filesystem
if ( @file_put_contents( $filename, $dataStr ) !== strlen( $dataStr ) )
{
throw new ezcBaseFileIoException( $filename, ezcBaseFileException::WRITE, 'Could not write data to cache file.' );
}
// Tries to set the file permissions
if ( ezcBaseFeatures::os() !== "Windows" )
{
chmod( $filename, $this->options->permissions );
}
// Prepares the data for APC storage
$dataObj = $this->prepareData( $data, true );
$dataObj->mtime = @filemtime( $filename );
$dataObj->atime = time();
// Stores it in APC
$this->registerIdentifier( $id, $attributes, $filename );
if ( !$this->backend->store( $filename, $dataObj, $this->properties['options']['ttl'] ) )
{
throw new ezcCacheApcException( "APC store failed." );
}
// Returns the ID for no good reason
return $id;
}
/**
* Restores the data from the cache.
*
* @param string $id The item ID to restore
* @param array(string=>string) $attributes Attributes describing the data to restore
* @param bool $search Whether to search for items if not found directly
* @return mixed The cached data on success, otherwise false
*/
public function restore( $id, $attributes = array(), $search = false )
{
// Generates the identifier
$filename = $this->properties['location'] . $this->generateIdentifier( $id, $attributes );
// Grabs the data object from the APC
$dataObj = $this->fetchObject( $filename );
$useApc = false;
// Checks the APC object exists
if ( $dataObj !== false
&& is_object( $dataObj )
&& isset( $dataObj->atime ) )
{
$useApc = true;
}
// Checks the APC object is still valid
if ( !isset( $this->registry[$filename] )
&& $useApc === true
&& time() === $dataObj->atime )
{
// Make sure the FileSystem still has the file and that it hasn't changed
if ( file_exists( $filename ) !== false
&& @filemtime( $filename ) === $dataObj->mtime )
{
$dataObj->atime = time();
$this->backend->store( $filename, $dataObj, $this->properties['options']['ttl'] );
}
else
{
$useApc = false;
$this->backend->delete( $filename );
}
}
// Searches the filesystem for the file
if ( !isset( $this->registry[$filename] )
&& $useApc === false
&& file_exists( $filename ) === false )
{
if ( $search === true
&& count( $files = $this->search( $id, $attributes ) ) === 1 )
{
$filename = $files[0][2];
}
else
{
// There are more elements found during search, so false is returned
return false;
}
}
// Returns false if no data is stored anywhere
if ( $useApc === false
&& file_exists( $filename ) === false )
{
// Purges APC
$this->backend->delete( $filename );
// Purges Registry Cache
if ( isset( $this->registry[$filename] ) )
{
unset( $this->registry[$filename] );
}
return false;
}
// Creates a Registry Object -- should only happen once per page load
if ( !isset( $this->registry[$filename] ) )
{
$this->registry[$filename] = new stdClass();
$this->registry[$filename]->data = false;
$this->registry[$filename]->mtime = $useApc ? $dataObj->mtime : null;
$this->registry[$filename]->lifetime = $this->calcLifetime( $filename, $useApc );
}
// Purges the data if it is expired
if ( $this->properties['options']['ttl'] !== false
&& $this->calcLifetime( $filename, $useApc ) > $this->properties['options']['ttl'] )
{
$this->delete( $id, $attributes, false ); // don't search
return false;
}
// Returns the data from the Registry Cache
if ( $this->registry[$filename]->data !== false )
{
return ( $this->registry[$filename]->data );
}
// Returns data from APC
else if ( $useApc === true
&& ( isset( $dataObj->data ) || is_null( $dataObj->data ) ) )
{
$this->registry[$filename]->data = $dataObj->data; // primes the Registry cache
return ( $dataObj->data );
}
// Returns data from the filesystem
else if ( file_exists( $filename ) !== false )
{
// Grabs the data from the filesystem
$dataStr = $this->fetchData( $filename );
// Stores it in the Registry Cache
$this->registry[$filename]->data = $dataStr;
// Prepares the data for APC storage
$dataObj = $this->prepareData( $dataStr, true );
$dataObj->mtime = @filemtime( $filename );
$dataObj->atime = time();
// Stores it in APC
$this->backend->store( $filename, $dataObj, $this->properties['options']['ttl'] );
// Returns the data
return ( $dataStr );
}
else
{
return false;
}
}
/**
* Deletes the data associated with $id or $attributes from the cache.
*
* @throws ezcBaseFilePermissionException
* If an already existsing cache file could not be unlinked.
* This exception means most likely that your cache directory
* has been corrupted by external influences (file permission
* change).
*
* @param string $id The item ID to purge
* @param array(string=>string) $attributes Attributes describing the data to restore
* @param bool $search Whether to search for items if not found directly
*/
public function delete( $id = null, $attributes = array(), $search = false )
{
$location = $this->properties['location'];
// Generates the identifier
$filename = $location . $this->generateIdentifier( $id, $attributes );
// Initializes the array
$delFiles = array();
clearstatcache();
// Checks if the file exists on the filesystem
if ( file_exists( $filename ) )
{
$delFiles[] = array( $id, $attributes, $filename );
}
else if ( $search === true )
{
$delFiles = $this->search( $id, $attributes );
}
$deletedIds = array();
// Deletes the files
foreach ( $delFiles as $count => $filename )
{
// Deletes from Registry Cache
if ( isset( $this->registry[$filename[2]] ) )
{
unset( $this->registry[$filename[2]] );
}
// Deletes from APC
$this->backend->delete( $filename[2] );
$this->unRegisterIdentifier( $filename[0], $filename[1], $filename[2], true );
if ( isset( $this->registry[$location][$filename[0]][$filename[2]] ) )
{
unset( $this->registry[$location][$filename[0]][$filename[2]] );
}
// Deletes from the filesystem
if ( @unlink( $filename[2] ) === false )
{
throw new ezcBaseFilePermissionException(
$filename,
ezcBaseFileException::WRITE,
'Could not unlink cache file.'
);
}
$deletedIds[] = $filename[0];
}
$this->storeSearchRegistry();
return $deletedIds;
}
/**
* Checks the path in the location property exists, and is read-/writable. It
* throws an exception if not.
*
* @throws ezcBaseFileNotFoundException
* If the storage location does not exist. This should usually not
* happen, since {@link ezcCacheManager::createCache()} already
* performs sanity checks for the cache location. In case this
* exception is thrown, your cache location has been corrupted
* after the cache was configured.
* @throws ezcBaseFileNotFoundException
* If the storage location is not a directory. This should usually
* not happen, since {@link ezcCacheManager::createCache()} already
* performs sanity checks for the cache location. In case this
* exception is thrown, your cache location has been corrupted
* after the cache was configured.
* @throws ezcBaseFilePermissionException
* If the storage location is not writeable. This should usually not
* happen, since {@link ezcCacheManager::createCache()} already
* performs sanity checks for the cache location. In case this
* exception is thrown, your cache location has been corrupted
* after the cache was configured.
*/
protected function validateLocation()
{
if ( file_exists( $this->properties['location'] ) === false )
{
throw new ezcBaseFileNotFoundException( $this->properties['location'], 'cache location' );
}
if ( is_dir( $this->properties['location'] ) === false )
{
throw new ezcBaseFileNotFoundException( $this->properties['location'], 'cache location', 'Cache location not a directory.' );
}
if ( is_writeable( $this->properties['location'] ) === false )
{
throw new ezcBaseFilePermissionException( $this->properties['location'], ezcBaseFileException::WRITE, 'Cache location is not a directory.' );
}
}
/**
* Calculates the lifetime remaining for a cache object.
*
* If the TTL option is set to false, this method will always return 1 for
* existing items.
*
* @param string $filename The file to calculate the remaining lifetime for
* @param bool $useApc Use APC or not
* @return int The remaining lifetime in seconds (0 if no time remaining)
*/
protected function calcLifetime( $filename, $useApc = false )
{
$ttl = $this->options->ttl;
// Calculate when the APC object was created
if ( $useApc === true )
{
// we've likely already looked this thing up in APC, so we'll grab the local object
if ( isset( $this->registry[$filename] ) )
{
$dataObj = $this->registry[$filename];
}
else // otherwise we'll grab it from APC
{
$dataObj = $this->fetchObject( $filename );
}
if ( is_object( $dataObj ) )
{
if ( $ttl === false )
{
return 1;
}
return (
( $lifeTime = ( time() - $dataObj->mtime ) ) > $ttl
? $ttl - $lifeTime
: 0
);
}
else
{
return 0;
}
}
// Calculate when the filesystem file was created
else
{
if ( ( file_exists( $filename ) !== false )
&& ( ( $modTime = @filemtime( $filename ) ) !== false ) )
{
if ( $ttl === false )
{
return 1;
}
return (
( $lifeTime = ( time() - $modTime ) ) < $ttl
? $ttl - $lifeTime
: 0
);
}
else
{
return 0;
}
}
}
}
?>