2018-04-17 02:11:51 +00: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\Adapter ;
use Psr\Cache\CacheItemInterface ;
use Psr\Log\LoggerAwareInterface ;
use Psr\Log\LoggerInterface ;
use Symfony\Component\Cache\CacheItem ;
use Symfony\Component\Cache\Exception\InvalidArgumentException ;
use Symfony\Component\Cache\ResettableInterface ;
use Symfony\Component\Cache\Traits\AbstractTrait ;
/**
* @ author Nicolas Grekas < p @ tchwork . com >
*/
abstract class AbstractAdapter implements AdapterInterface , LoggerAwareInterface , ResettableInterface
{
2024-01-12 05:08:24 +00:00
/**
* @ internal
*/
const NS_SEPARATOR = ':' ;
2018-04-17 02:11:51 +00:00
use AbstractTrait ;
private static $apcuSupported ;
private static $phpFilesSupported ;
private $createCacheItem ;
private $mergeByLifetime ;
/**
* @ param string $namespace
* @ param int $defaultLifetime
*/
protected function __construct ( $namespace = '' , $defaultLifetime = 0 )
{
2024-01-12 05:08:24 +00:00
$this -> namespace = '' === $namespace ? '' : CacheItem :: validateKey ( $namespace ) . static :: NS_SEPARATOR ;
if ( null !== $this -> maxIdLength && \strlen ( $namespace ) > $this -> maxIdLength - 24 ) {
throw new InvalidArgumentException ( sprintf ( 'Namespace must be %d chars max, %d given ("%s").' , $this -> maxIdLength - 24 , \strlen ( $namespace ), $namespace ));
2018-04-17 02:11:51 +00:00
}
$this -> createCacheItem = \Closure :: bind (
2024-01-12 05:08:24 +00:00
static function ( $key , $value , $isHit ) {
2018-04-17 02:11:51 +00:00
$item = new CacheItem ();
$item -> key = $key ;
$item -> value = $value ;
$item -> isHit = $isHit ;
return $item ;
},
null ,
CacheItem :: class
);
$getId = function ( $key ) { return $this -> getId (( string ) $key ); };
$this -> mergeByLifetime = \Closure :: bind (
2024-01-12 05:08:24 +00:00
static function ( $deferred , $namespace , & $expiredIds ) use ( $getId , $defaultLifetime ) {
$byLifetime = [];
2018-04-17 02:11:51 +00:00
$now = time ();
2024-01-12 05:08:24 +00:00
$expiredIds = [];
2018-04-17 02:11:51 +00:00
foreach ( $deferred as $key => $item ) {
if ( null === $item -> expiry ) {
2024-01-12 05:08:24 +00:00
$byLifetime [ 0 < $defaultLifetime ? $defaultLifetime : 0 ][ $getId ( $key )] = $item -> value ;
} elseif ( 0 === $item -> expiry ) {
$byLifetime [ 0 ][ $getId ( $key )] = $item -> value ;
2018-04-17 02:11:51 +00:00
} elseif ( $item -> expiry > $now ) {
$byLifetime [ $item -> expiry - $now ][ $getId ( $key )] = $item -> value ;
} else {
$expiredIds [] = $getId ( $key );
}
}
return $byLifetime ;
},
null ,
CacheItem :: class
);
}
/**
2024-01-12 05:08:24 +00:00
* @ param string $namespace
* @ param int $defaultLifetime
* @ param string $version
* @ param string $directory
2018-04-17 02:11:51 +00:00
*
* @ return AdapterInterface
*/
public static function createSystemCache ( $namespace , $defaultLifetime , $version , $directory , LoggerInterface $logger = null )
{
if ( null === self :: $apcuSupported ) {
self :: $apcuSupported = ApcuAdapter :: isSupported ();
}
if ( ! self :: $apcuSupported && null === self :: $phpFilesSupported ) {
self :: $phpFilesSupported = PhpFilesAdapter :: isSupported ();
}
if ( self :: $phpFilesSupported ) {
$opcache = new PhpFilesAdapter ( $namespace , $defaultLifetime , $directory );
if ( null !== $logger ) {
$opcache -> setLogger ( $logger );
}
return $opcache ;
}
$fs = new FilesystemAdapter ( $namespace , $defaultLifetime , $directory );
if ( null !== $logger ) {
$fs -> setLogger ( $logger );
}
2024-01-12 05:08:24 +00:00
if ( ! self :: $apcuSupported || ( \in_array ( \PHP_SAPI , [ 'cli' , 'phpdbg' ], true ) && ! filter_var ( ini_get ( 'apc.enable_cli' ), \FILTER_VALIDATE_BOOLEAN ))) {
2018-04-17 02:11:51 +00:00
return $fs ;
}
$apcu = new ApcuAdapter ( $namespace , ( int ) $defaultLifetime / 5 , $version );
2024-01-12 05:08:24 +00:00
if ( null !== $logger ) {
2018-04-17 02:11:51 +00:00
$apcu -> setLogger ( $logger );
}
2024-01-12 05:08:24 +00:00
return new ChainAdapter ([ $apcu , $fs ]);
2018-04-17 02:11:51 +00:00
}
2024-01-12 05:08:24 +00:00
public static function createConnection ( $dsn , array $options = [])
2018-04-17 02:11:51 +00:00
{
2024-01-12 05:08:24 +00:00
if ( ! \is_string ( $dsn )) {
throw new InvalidArgumentException ( sprintf ( 'The "%s()" method expect argument #1 to be string, "%s" given.' , __METHOD__ , \gettype ( $dsn )));
2018-04-17 02:11:51 +00:00
}
if ( 0 === strpos ( $dsn , 'redis://' )) {
return RedisAdapter :: createConnection ( $dsn , $options );
}
if ( 0 === strpos ( $dsn , 'memcached://' )) {
return MemcachedAdapter :: createConnection ( $dsn , $options );
}
2024-01-12 05:08:24 +00:00
throw new InvalidArgumentException ( sprintf ( 'Unsupported DSN: "%s".' , $dsn ));
2018-04-17 02:11:51 +00:00
}
/**
* { @ inheritdoc }
*/
public function getItem ( $key )
{
if ( $this -> deferred ) {
$this -> commit ();
}
$id = $this -> getId ( $key );
$f = $this -> createCacheItem ;
$isHit = false ;
$value = null ;
try {
2024-01-12 05:08:24 +00:00
foreach ( $this -> doFetch ([ $id ]) as $value ) {
2018-04-17 02:11:51 +00:00
$isHit = true ;
}
} catch ( \Exception $e ) {
2024-01-12 05:08:24 +00:00
CacheItem :: log ( $this -> logger , 'Failed to fetch key "{key}"' , [ 'key' => $key , 'exception' => $e ]);
2018-04-17 02:11:51 +00:00
}
return $f ( $key , $value , $isHit );
}
/**
* { @ inheritdoc }
*/
2024-01-12 05:08:24 +00:00
public function getItems ( array $keys = [])
2018-04-17 02:11:51 +00:00
{
if ( $this -> deferred ) {
$this -> commit ();
}
2024-01-12 05:08:24 +00:00
$ids = [];
2018-04-17 02:11:51 +00:00
foreach ( $keys as $key ) {
$ids [] = $this -> getId ( $key );
}
try {
$items = $this -> doFetch ( $ids );
} catch ( \Exception $e ) {
2024-01-12 05:08:24 +00:00
CacheItem :: log ( $this -> logger , 'Failed to fetch requested items' , [ 'keys' => $keys , 'exception' => $e ]);
$items = [];
2018-04-17 02:11:51 +00:00
}
$ids = array_combine ( $ids , $keys );
return $this -> generateItems ( $items , $ids );
}
/**
* { @ inheritdoc }
*/
public function save ( CacheItemInterface $item )
{
if ( ! $item instanceof CacheItem ) {
return false ;
}
$this -> deferred [ $item -> getKey ()] = $item ;
return $this -> commit ();
}
/**
* { @ inheritdoc }
*/
public function saveDeferred ( CacheItemInterface $item )
{
if ( ! $item instanceof CacheItem ) {
return false ;
}
$this -> deferred [ $item -> getKey ()] = $item ;
return true ;
}
/**
* { @ inheritdoc }
*/
public function commit ()
{
$ok = true ;
$byLifetime = $this -> mergeByLifetime ;
$byLifetime = $byLifetime ( $this -> deferred , $this -> namespace , $expiredIds );
2024-01-12 05:08:24 +00:00
$retry = $this -> deferred = [];
2018-04-17 02:11:51 +00:00
if ( $expiredIds ) {
$this -> doDelete ( $expiredIds );
}
foreach ( $byLifetime as $lifetime => $values ) {
try {
$e = $this -> doSave ( $values , $lifetime );
} catch ( \Exception $e ) {
}
2024-01-12 05:08:24 +00:00
if ( true === $e || [] === $e ) {
2018-04-17 02:11:51 +00:00
continue ;
}
2024-01-12 05:08:24 +00:00
if ( \is_array ( $e ) || 1 === \count ( $values )) {
foreach ( \is_array ( $e ) ? $e : array_keys ( $values ) as $id ) {
2018-04-17 02:11:51 +00:00
$ok = false ;
$v = $values [ $id ];
2024-01-12 05:08:24 +00:00
$type = \is_object ( $v ) ? \get_class ( $v ) : \gettype ( $v );
CacheItem :: log ( $this -> logger , 'Failed to save key "{key}" ({type})' , [ 'key' => substr ( $id , \strlen ( $this -> namespace )), 'type' => $type , 'exception' => $e instanceof \Exception ? $e : null ]);
2018-04-17 02:11:51 +00:00
}
} else {
foreach ( $values as $id => $v ) {
$retry [ $lifetime ][] = $id ;
}
}
}
// When bulk-save failed, retry each item individually
foreach ( $retry as $lifetime => $ids ) {
foreach ( $ids as $id ) {
try {
$v = $byLifetime [ $lifetime ][ $id ];
2024-01-12 05:08:24 +00:00
$e = $this -> doSave ([ $id => $v ], $lifetime );
2018-04-17 02:11:51 +00:00
} catch ( \Exception $e ) {
}
2024-01-12 05:08:24 +00:00
if ( true === $e || [] === $e ) {
2018-04-17 02:11:51 +00:00
continue ;
}
$ok = false ;
2024-01-12 05:08:24 +00:00
$type = \is_object ( $v ) ? \get_class ( $v ) : \gettype ( $v );
CacheItem :: log ( $this -> logger , 'Failed to save key "{key}" ({type})' , [ 'key' => substr ( $id , \strlen ( $this -> namespace )), 'type' => $type , 'exception' => $e instanceof \Exception ? $e : null ]);
2018-04-17 02:11:51 +00:00
}
}
return $ok ;
}
2024-01-12 05:08:24 +00:00
public function __sleep ()
{
throw new \BadMethodCallException ( 'Cannot serialize ' . __CLASS__ );
}
public function __wakeup ()
{
throw new \BadMethodCallException ( 'Cannot unserialize ' . __CLASS__ );
}
2018-04-17 02:11:51 +00:00
public function __destruct ()
{
if ( $this -> deferred ) {
$this -> commit ();
}
}
private function generateItems ( $items , & $keys )
{
$f = $this -> createCacheItem ;
try {
foreach ( $items as $id => $value ) {
if ( ! isset ( $keys [ $id ])) {
$id = key ( $keys );
}
$key = $keys [ $id ];
unset ( $keys [ $id ]);
yield $key => $f ( $key , $value , true );
}
} catch ( \Exception $e ) {
2024-01-12 05:08:24 +00:00
CacheItem :: log ( $this -> logger , 'Failed to fetch requested items' , [ 'keys' => array_values ( $keys ), 'exception' => $e ]);
2018-04-17 02:11:51 +00:00
}
foreach ( $keys as $key ) {
yield $key => $f ( $key , null , false );
}
}
}