2018-04-17 04:11:51 +02:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Cache\Traits ;
use Psr\Log\LoggerAwareTrait ;
use Symfony\Component\Cache\CacheItem ;
/**
* @ author Nicolas Grekas < p @ tchwork . com >
*
* @ internal
*/
trait AbstractTrait
{
use LoggerAwareTrait ;
private $namespace ;
private $namespaceVersion = '' ;
private $versioningIsEnabled = false ;
2024-01-12 06:08:24 +01:00
private $deferred = [];
2024-03-20 03:35:09 +01:00
private $ids = [];
2018-04-17 04:11:51 +02:00
/**
* @ var int | null The maximum length to enforce for identifiers or null when no limit applies
*/
protected $maxIdLength ;
/**
* Fetches several cache items .
*
* @ param array $ids The cache identifiers to fetch
*
* @ return array | \Traversable The corresponding values found in the cache
*/
abstract protected function doFetch ( array $ids );
/**
* Confirms if the cache contains specified cache item .
*
* @ param string $id The identifier for which to check existence
*
* @ return bool True if item exists in the cache , false otherwise
*/
abstract protected function doHave ( $id );
/**
* Deletes all items in the pool .
*
2024-01-12 06:08:24 +01:00
* @ param string $namespace The prefix used for all identifiers managed by this pool
2018-04-17 04:11:51 +02:00
*
* @ return bool True if the pool was successfully cleared , false otherwise
*/
abstract protected function doClear ( $namespace );
/**
* Removes multiple items from the pool .
*
* @ param array $ids An array of identifiers that should be removed from the pool
*
* @ return bool True if the items were successfully removed , false otherwise
*/
abstract protected function doDelete ( array $ids );
/**
* Persists several cache items immediately .
*
* @ param array $values The values to cache , indexed by their cache identifier
* @ param int $lifetime The lifetime of the cached values , 0 for persisting until manual cleaning
*
* @ return array | bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
*/
2024-03-20 03:35:09 +01:00
abstract protected function doSave ( array $values , int $lifetime );
2018-04-17 04:11:51 +02:00
/**
* { @ inheritdoc }
2024-03-20 03:35:09 +01:00
*
* @ return bool
2018-04-17 04:11:51 +02:00
*/
public function hasItem ( $key )
{
$id = $this -> getId ( $key );
if ( isset ( $this -> deferred [ $key ])) {
$this -> commit ();
}
try {
return $this -> doHave ( $id );
} catch ( \Exception $e ) {
2024-03-20 03:35:09 +01:00
CacheItem :: log ( $this -> logger , 'Failed to check if key "{key}" is cached: ' . $e -> getMessage (), [ 'key' => $key , 'exception' => $e ]);
2018-04-17 04:11:51 +02:00
return false ;
}
}
/**
* { @ inheritdoc }
2024-03-20 03:35:09 +01:00
*
* @ param string $prefix
*
* @ return bool
2018-04-17 04:11:51 +02:00
*/
2024-03-20 03:35:09 +01:00
public function clear ( /* string $prefix = '' */ )
2018-04-17 04:11:51 +02:00
{
2024-01-12 06:08:24 +01:00
$this -> deferred = [];
2018-04-17 04:11:51 +02:00
if ( $cleared = $this -> versioningIsEnabled ) {
2024-03-20 03:35:09 +01:00
if ( '' === $namespaceVersionToClear = $this -> namespaceVersion ) {
foreach ( $this -> doFetch ([ static :: NS_SEPARATOR . $this -> namespace ]) as $v ) {
$namespaceVersionToClear = $v ;
}
}
$namespaceToClear = $this -> namespace . $namespaceVersionToClear ;
$namespaceVersion = self :: formatNamespaceVersion ( mt_rand ());
2024-01-12 06:08:24 +01:00
try {
2024-03-20 03:35:09 +01:00
$e = $this -> doSave ([ static :: NS_SEPARATOR . $this -> namespace => $namespaceVersion ], 0 );
2024-01-12 06:08:24 +01:00
} catch ( \Exception $e ) {
}
2024-03-20 03:35:09 +01:00
if ( true !== $e && [] !== $e ) {
$cleared = false ;
$message = 'Failed to save the new namespace' . ( $e instanceof \Exception ? ': ' . $e -> getMessage () : '.' );
CacheItem :: log ( $this -> logger , $message , [ 'exception' => $e instanceof \Exception ? $e : null ]);
} else {
2024-01-12 06:08:24 +01:00
$this -> namespaceVersion = $namespaceVersion ;
2024-03-20 03:35:09 +01:00
$this -> ids = [];
2018-04-17 04:11:51 +02:00
}
2024-03-20 03:35:09 +01:00
} else {
$prefix = 0 < \func_num_args () ? ( string ) func_get_arg ( 0 ) : '' ;
$namespaceToClear = $this -> namespace . $prefix ;
2018-04-17 04:11:51 +02:00
}
try {
2024-03-20 03:35:09 +01:00
return $this -> doClear ( $namespaceToClear ) || $cleared ;
2018-04-17 04:11:51 +02:00
} catch ( \Exception $e ) {
2024-03-20 03:35:09 +01:00
CacheItem :: log ( $this -> logger , 'Failed to clear the cache: ' . $e -> getMessage (), [ 'exception' => $e ]);
2018-04-17 04:11:51 +02:00
return false ;
}
}
/**
* { @ inheritdoc }
2024-03-20 03:35:09 +01:00
*
* @ return bool
2018-04-17 04:11:51 +02:00
*/
public function deleteItem ( $key )
{
2024-01-12 06:08:24 +01:00
return $this -> deleteItems ([ $key ]);
2018-04-17 04:11:51 +02:00
}
/**
* { @ inheritdoc }
2024-03-20 03:35:09 +01:00
*
* @ return bool
2018-04-17 04:11:51 +02:00
*/
public function deleteItems ( array $keys )
{
2024-01-12 06:08:24 +01:00
$ids = [];
2018-04-17 04:11:51 +02:00
foreach ( $keys as $key ) {
$ids [ $key ] = $this -> getId ( $key );
unset ( $this -> deferred [ $key ]);
}
try {
if ( $this -> doDelete ( $ids )) {
return true ;
}
} catch ( \Exception $e ) {
}
$ok = true ;
// When bulk-delete failed, retry each item individually
foreach ( $ids as $key => $id ) {
try {
$e = null ;
2024-01-12 06:08:24 +01:00
if ( $this -> doDelete ([ $id ])) {
2018-04-17 04:11:51 +02:00
continue ;
}
} catch ( \Exception $e ) {
}
2024-03-20 03:35:09 +01:00
$message = 'Failed to delete key "{key}"' . ( $e instanceof \Exception ? ': ' . $e -> getMessage () : '.' );
CacheItem :: log ( $this -> logger , $message , [ 'key' => $key , 'exception' => $e ]);
2018-04-17 04:11:51 +02:00
$ok = false ;
}
return $ok ;
}
/**
* Enables / disables versioning of items .
*
* When versioning is enabled , clearing the cache is atomic and doesn ' t require listing existing keys to proceed ,
* but old keys may need garbage collection and extra round - trips to the back - end are required .
*
* Calling this method also clears the memoized namespace version and thus forces a resynchonization of it .
*
* @ param bool $enable
*
* @ return bool the previous state of versioning
*/
public function enableVersioning ( $enable = true )
{
$wasEnabled = $this -> versioningIsEnabled ;
$this -> versioningIsEnabled = ( bool ) $enable ;
$this -> namespaceVersion = '' ;
2024-03-20 03:35:09 +01:00
$this -> ids = [];
2018-04-17 04:11:51 +02:00
return $wasEnabled ;
}
/**
* { @ inheritdoc }
*/
public function reset ()
{
if ( $this -> deferred ) {
$this -> commit ();
}
$this -> namespaceVersion = '' ;
2024-03-20 03:35:09 +01:00
$this -> ids = [];
2018-04-17 04:11:51 +02:00
}
/**
* Like the native unserialize () function but throws an exception if anything goes wrong .
*
* @ param string $value
*
* @ return mixed
*
* @ throws \Exception
2024-03-20 03:35:09 +01:00
*
* @ deprecated since Symfony 4.2 , use DefaultMarshaller instead .
2018-04-17 04:11:51 +02:00
*/
protected static function unserialize ( $value )
{
2024-03-20 03:35:09 +01:00
@ trigger_error ( sprintf ( 'The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.' , __CLASS__ ), \E_USER_DEPRECATED );
2018-04-17 04:11:51 +02:00
if ( 'b:0;' === $value ) {
return false ;
}
$unserializeCallbackHandler = ini_set ( 'unserialize_callback_func' , __CLASS__ . '::handleUnserializeCallback' );
try {
if ( false !== $value = unserialize ( $value )) {
return $value ;
}
2024-01-12 06:08:24 +01:00
throw new \DomainException ( 'Failed to unserialize cached value.' );
2018-04-17 04:11:51 +02:00
} catch ( \Error $e ) {
2024-01-12 06:08:24 +01:00
throw new \ErrorException ( $e -> getMessage (), $e -> getCode (), \E_ERROR , $e -> getFile (), $e -> getLine ());
2018-04-17 04:11:51 +02:00
} finally {
ini_set ( 'unserialize_callback_func' , $unserializeCallbackHandler );
}
}
2024-03-20 03:35:09 +01:00
private function getId ( $key ) : string
2018-04-17 04:11:51 +02:00
{
if ( $this -> versioningIsEnabled && '' === $this -> namespaceVersion ) {
2024-03-20 03:35:09 +01:00
$this -> ids = [];
2024-01-12 06:08:24 +01:00
$this -> namespaceVersion = '1' . static :: NS_SEPARATOR ;
try {
foreach ( $this -> doFetch ([ static :: NS_SEPARATOR . $this -> namespace ]) as $v ) {
$this -> namespaceVersion = $v ;
}
2024-03-20 03:35:09 +01:00
$e = true ;
2024-01-12 06:08:24 +01:00
if ( '1' . static :: NS_SEPARATOR === $this -> namespaceVersion ) {
2024-03-20 03:35:09 +01:00
$this -> namespaceVersion = self :: formatNamespaceVersion ( time ());
$e = $this -> doSave ([ static :: NS_SEPARATOR . $this -> namespace => $this -> namespaceVersion ], 0 );
2024-01-12 06:08:24 +01:00
}
} catch ( \Exception $e ) {
2018-04-17 04:11:51 +02:00
}
2024-03-20 03:35:09 +01:00
if ( true !== $e && [] !== $e ) {
$message = 'Failed to save the new namespace' . ( $e instanceof \Exception ? ': ' . $e -> getMessage () : '.' );
CacheItem :: log ( $this -> logger , $message , [ 'exception' => $e instanceof \Exception ? $e : null ]);
}
}
if ( \is_string ( $key ) && isset ( $this -> ids [ $key ])) {
return $this -> namespace . $this -> namespaceVersion . $this -> ids [ $key ];
}
CacheItem :: validateKey ( $key );
$this -> ids [ $key ] = $key ;
if ( \count ( $this -> ids ) > 1000 ) {
$this -> ids = \array_slice ( $this -> ids , 500 , null , true ); // stop memory leak if there are many keys
2018-04-17 04:11:51 +02:00
}
if ( null === $this -> maxIdLength ) {
return $this -> namespace . $this -> namespaceVersion . $key ;
}
2024-01-12 06:08:24 +01:00
if ( \strlen ( $id = $this -> namespace . $this -> namespaceVersion . $key ) > $this -> maxIdLength ) {
2024-03-20 03:35:09 +01:00
// Use MD5 to favor speed over security, which is not an issue here
$this -> ids [ $key ] = $id = substr_replace ( base64_encode ( hash ( 'md5' , $key , true )), static :: NS_SEPARATOR , - ( \strlen ( $this -> namespaceVersion ) + 2 ));
$id = $this -> namespace . $this -> namespaceVersion . $id ;
2018-04-17 04:11:51 +02:00
}
return $id ;
}
/**
* @ internal
*/
public static function handleUnserializeCallback ( $class )
{
throw new \DomainException ( 'Class not found: ' . $class );
}
2024-03-20 03:35:09 +01:00
private static function formatNamespaceVersion ( int $value ) : string
{
return strtr ( substr_replace ( base64_encode ( pack ( 'V' , $value )), static :: NS_SEPARATOR , 5 ), '/' , '_' );
}
2018-04-17 04:11:51 +02:00
}