diff --git a/application/logintwig.class.inc.php b/application/logintwig.class.inc.php
index b800e2e80..b7d411b1d 100644
--- a/application/logintwig.class.inc.php
+++ b/application/logintwig.class.inc.php
@@ -9,6 +9,9 @@
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\TwigBase\Twig\Extension;
+use Twig\Environment;
+use Twig\Loader\ChainLoader;
+use Twig\Loader\FilesystemLoader;
/**
* Twig context for modules extending the login screen
@@ -217,14 +220,14 @@ class LoginTwigRenderer
$sTwigLoaderPath = $oLoginContext->GetTwigLoaderPath();
if ($sTwigLoaderPath != null)
{
- $oExtensionLoader = new Twig_Loader_Filesystem();
+ $oExtensionLoader = new FilesystemLoader();
$oExtensionLoader->setPaths($sTwigLoaderPath);
$aTwigLoaders[] = $oExtensionLoader;
}
$this->aPostedVars = array_merge($this->aPostedVars, $oLoginContext->GetPostedVars());
}
- $oCoreLoader = new Twig_Loader_Filesystem(array(), APPROOT.'templates');
+ $oCoreLoader = new FilesystemLoader(array(), APPROOT.'templates');
$aCoreTemplatesPaths = array('pages/login', 'pages/login/password');
// Having this path declared after the plugins let the plugins replace the core templates
$oCoreLoader->setPaths($aCoreTemplatesPaths);
@@ -232,8 +235,8 @@ class LoginTwigRenderer
$oCoreLoader->setPaths($aCoreTemplatesPaths, 'ItopCore');
$aTwigLoaders[] = $oCoreLoader;
- $oLoader = new Twig_Loader_Chain($aTwigLoaders);
- $this->oTwig = new Twig_Environment($oLoader);
+ $oLoader = new ChainLoader($aTwigLoaders);
+ $this->oTwig = new Environment($oLoader);
Extension::RegisterTwigExtensions($this->oTwig);
}
@@ -306,7 +309,7 @@ class LoginTwigRenderer
}
/**
- * @return \Twig_Environment
+ * @return \Twig\Environment
*/
public function GetTwig()
{
diff --git a/application/twigextension.class.inc.php b/application/twigextension.class.inc.php
index d0a92aab4..5234eb96a 100644
--- a/application/twigextension.class.inc.php
+++ b/application/twigextension.class.inc.php
@@ -8,9 +8,9 @@ use DeprecatedCallsLog;
use Dict;
use Exception;
use MetaModel;
-use Twig_Environment;
-use Twig_SimpleFilter;
-use Twig_SimpleFunction;
+use Twig\Environment;
+use Twig\TwigFilter;
+use Twig\TwigFunction;
use utils;
DeprecatedCallsLog::NotifyDeprecatedFile('instead use sources/Application/TwigBase/Twig/Extension.php, which is loaded by the autoloader');
@@ -28,13 +28,13 @@ class TwigExtension
* Registers Twig extensions such as filters or functions.
* It allows us to access some stuff directly in twig.
*
- * @param \Twig_Environment $oTwigEnv
+ * @param Environment $oTwigEnv
*/
- public static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv)
+ public static function RegisterTwigExtensions(Environment &$oTwigEnv)
{
// Filter to translate a string via the Dict::S function
// Usage in twig: {{ 'String:ToTranslate'|dict_s }}
- $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s',
+ $oTwigEnv->addFilter(new TwigFilter('dict_s',
function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) {
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
})
@@ -42,7 +42,7 @@ class TwigExtension
// Filter to format a string via the Dict::Format function
// Usage in twig: {{ 'String:ToTranslate'|dict_format() }}
- $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format',
+ $oTwigEnv->addFilter(new TwigFilter('dict_format',
function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) {
return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
})
@@ -51,16 +51,13 @@ class TwigExtension
// Filter to format output
// example a DateTime is converted to user format
// Usage in twig: {{ 'String:ToFormat'|output_format }}
- $oTwigEnv->addFilter(new Twig_SimpleFilter('date_format',
+ $oTwigEnv->addFilter(new TwigFilter('date_format',
function ($sDate) {
- try
- {
- if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate)))
- {
+ try {
+ if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate))) {
return AttributeDateTime::GetFormat()->Format($sDate);
}
- if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate)))
- {
+ if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate))) {
return AttributeDate::GetFormat()->Format($sDate);
}
}
@@ -75,7 +72,7 @@ class TwigExtension
// Filter to format output
// example a DateTime is converted to user format
// Usage in twig: {{ 'String:ToFormat'|output_format }}
- $oTwigEnv->addFilter(new Twig_SimpleFilter('size_format',
+ $oTwigEnv->addFilter(new TwigFilter('size_format',
function ($sSize) {
return utils::BytesToFriendlyFormat($sSize);
})
@@ -83,24 +80,25 @@ class TwigExtension
// Filter to enable base64 encode/decode
// Usage in twig: {{ 'String to encode'|base64_encode }}
- $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
- $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
+ $oTwigEnv->addFilter(new TwigFilter('base64_encode', 'base64_encode'));
+ $oTwigEnv->addFilter(new TwigFilter('base64_decode', 'base64_decode'));
// Filter to enable json decode (encode already exists)
// Usage in twig: {{ aSomeArray|json_decode }}
- $oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function ($sJsonString, $bAssoc = false) {
+ $oTwigEnv->addFilter(new TwigFilter('json_decode', function ($sJsonString, $bAssoc = false) {
return json_decode($sJsonString, $bAssoc);
})
);
// Filter to add itopversion to an url
- $oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) {
+ $oTwigEnv->addFilter(new TwigFilter('add_itop_version', function ($sUrl) {
$sUrl = utils::AddParameterToUrl($sUrl, 'itopversion', ITOP_VERSION);
+
return $sUrl;
}));
// Filter to add a module's version to an url
- $oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) {
+ $oTwigEnv->addFilter(new TwigFilter('add_module_version', function ($sUrl, $sModuleName) {
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
$sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion);
@@ -109,38 +107,36 @@ class TwigExtension
// Function to check our current environment
// Usage in twig: {% if is_development_environment() %}
- $oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function()
- {
+ $oTwigEnv->addFunction(new TwigFunction('is_development_environment', function () {
return utils::IsDevelopmentEnvironment();
}));
// Function to get configuration parameter
// Usage in twig: {{ get_config_parameter('foo') }}
- $oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function($sParamName)
- {
+ $oTwigEnv->addFunction(new TwigFunction('get_config_parameter', function ($sParamName) {
$oConfig = MetaModel::GetConfig();
+
return $oConfig->Get($sParamName);
}));
// Function to get a module setting
// Usage in twig: {{ get_module_setting(
+ */ +interface CacheInterface +{ + /** + * Fetches a value from the pool or computes it if not found. + * + * On cache misses, a callback is called that should return the missing value. + * This callback is given a PSR-6 CacheItemInterface instance corresponding to the + * requested key, that could be used e.g. for expiration control. It could also + * be an ItemInterface instance when its additional features are needed. + * + * @param string $key The key of the item to retrieve from the cache + * @param callable|CallbackInterface $callback Should return the computed value for the given key/item + * @param float|null $beta A float that, as it grows, controls the likeliness of triggering + * early expiration. 0 disables it, INF forces immediate expiration. + * The default (or providing null) is implementation dependent but should + * typically be 1.0, which should provide optimal stampede protection. + * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration + * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} + * + * @return mixed + * + * @throws InvalidArgumentException When $key is not valid or when $beta is negative + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null); + + /** + * Removes an item from the pool. + * + * @param string $key The key to delete + * + * @throws InvalidArgumentException When $key is not valid + * + * @return bool True if the item was successfully removed, false if there was any error + */ + public function delete(string $key): bool; +} diff --git a/lib/symfony/cache-contracts/CacheTrait.php b/lib/symfony/cache-contracts/CacheTrait.php new file mode 100644 index 000000000..d340e0696 --- /dev/null +++ b/lib/symfony/cache-contracts/CacheTrait.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(InvalidArgumentException::class); + +/** + * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. + * + * @author Nicolas Grekas
+ */ +trait CacheTrait +{ + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $key, callable $callback, float $beta = null, array &$metadata = null) + { + return $this->doGet($this, $key, $callback, $beta, $metadata); + } + + /** + * {@inheritdoc} + */ + public function delete(string $key): bool + { + return $this->deleteItem($key); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null, LoggerInterface $logger = null) + { + if (0 > $beta = $beta ?? 1.0) { + throw new class(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException { }; + } + + $item = $pool->getItem($key); + $recompute = !$item->isHit() || \INF === $beta; + $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; + + if (!$recompute && $metadata) { + $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; + $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; + + if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { + // force applying defaultLifetime to expiry + $item->expiresAt(null); + $logger && $logger->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ + 'key' => $key, + 'delta' => sprintf('%.1f', $expiry - $now), + ]); + } + } + + if ($recompute) { + $save = true; + $item->set($callback($item, $save)); + if ($save) { + $pool->save($item); + } + } + + return $item->get(); + } +} diff --git a/lib/symfony/cache-contracts/CallbackInterface.php b/lib/symfony/cache-contracts/CallbackInterface.php new file mode 100644 index 000000000..7dae2aac3 --- /dev/null +++ b/lib/symfony/cache-contracts/CallbackInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Computes and returns the cached value of an item. + * + * @author Nicolas Grekas
+ */ +interface CallbackInterface +{ + /** + * @param CacheItemInterface|ItemInterface $item The item to compute the value for + * @param bool &$save Should be set to false when the value should not be saved in the pool + * + * @return mixed The computed value for the passed item + */ + public function __invoke(CacheItemInterface $item, bool &$save); +} diff --git a/lib/symfony/cache-contracts/ItemInterface.php b/lib/symfony/cache-contracts/ItemInterface.php new file mode 100644 index 000000000..10c048897 --- /dev/null +++ b/lib/symfony/cache-contracts/ItemInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Augments PSR-6's CacheItemInterface with support for tags and metadata. + * + * @author Nicolas Grekas
+ */ +interface ItemInterface extends CacheItemInterface +{ + /** + * References the Unix timestamp stating when the item will expire. + */ + public const METADATA_EXPIRY = 'expiry'; + + /** + * References the time the item took to be created, in milliseconds. + */ + public const METADATA_CTIME = 'ctime'; + + /** + * References the list of tags that were assigned to the item, as string[]. + */ + public const METADATA_TAGS = 'tags'; + + /** + * Reserved characters that cannot be used in a key or tag. + */ + public const RESERVED_CHARACTERS = '{}()/\@:'; + + /** + * Adds a tag to a cache item. + * + * Tags are strings that follow the same validation rules as keys. + * + * @param string|string[] $tags A tag or array of tags + * + * @return $this + * + * @throws InvalidArgumentException When $tag is not valid + * @throws CacheException When the item comes from a pool that is not tag-aware + */ + public function tag($tags): self; + + /** + * Returns a list of metadata info that were saved alongside with the cached value. + * + * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. + */ + public function getMetadata(): array; +} diff --git a/lib/symfony/class-loader/LICENSE b/lib/symfony/cache-contracts/LICENSE similarity index 96% rename from lib/symfony/class-loader/LICENSE rename to lib/symfony/cache-contracts/LICENSE index 9e936ec04..74cdc2dbf 100644 --- a/lib/symfony/class-loader/LICENSE +++ b/lib/symfony/cache-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2018-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/symfony/cache-contracts/README.md b/lib/symfony/cache-contracts/README.md new file mode 100644 index 000000000..7085a6996 --- /dev/null +++ b/lib/symfony/cache-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Cache Contracts +======================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/lib/symfony/cache-contracts/TagAwareCacheInterface.php b/lib/symfony/cache-contracts/TagAwareCacheInterface.php new file mode 100644 index 000000000..7c4cf1111 --- /dev/null +++ b/lib/symfony/cache-contracts/TagAwareCacheInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Cache; + +use Psr\Cache\InvalidArgumentException; + +/** + * Allows invalidating cached items using tags. + * + * @author Nicolas Grekas
+ */ +interface TagAwareCacheInterface extends CacheInterface +{ + /** + * Invalidates cached items using tags. + * + * When implemented on a PSR-6 pool, invalidation should not apply + * to deferred items. Instead, they should be committed as usual. + * This allows replacing old tagged values by new ones without + * race conditions. + * + * @param string[] $tags An array of tags to invalidate + * + * @return bool True on success + * + * @throws InvalidArgumentException When $tags is not valid + */ + public function invalidateTags(array $tags); +} diff --git a/lib/symfony/cache-contracts/composer.json b/lib/symfony/cache-contracts/composer.json new file mode 100644 index 000000000..9f45e1780 --- /dev/null +++ b/lib/symfony/cache-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/cache-contracts", + "type": "library", + "description": "Generic abstractions related to caching", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Cache\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/lib/symfony/cache/.gitignore b/lib/symfony/cache/.gitignore deleted file mode 100644 index 5414c2c65..000000000 --- a/lib/symfony/cache/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -composer.lock -phpunit.xml -vendor/ diff --git a/lib/symfony/cache/Adapter/AbstractAdapter.php b/lib/symfony/cache/Adapter/AbstractAdapter.php index ab7dc9607..0d1c671fe 100644 --- a/lib/symfony/cache/Adapter/AbstractAdapter.php +++ b/lib/symfony/cache/Adapter/AbstractAdapter.php @@ -11,71 +11,80 @@ 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; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\CacheInterface; /** * @author Nicolas Grekas
*/ -abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { + use AbstractAdapterTrait; + use ContractsTrait; + /** * @internal */ - const NS_SEPARATOR = ':'; - - use AbstractTrait; + protected const NS_SEPARATOR = ':'; private static $apcuSupported; private static $phpFilesSupported; - private $createCacheItem; - private $mergeByLifetime; - - /** - * @param string $namespace - * @param int $defaultLifetime - */ - protected function __construct($namespace = '', $defaultLifetime = 0) + protected function __construct(string $namespace = '', int $defaultLifetime = 0) { $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR; + $this->defaultLifetime = $defaultLifetime; 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)); } - $this->createCacheItem = \Closure::bind( + self::$createCacheItem ?? self::$createCacheItem = \Closure::bind( static function ($key, $value, $isHit) { $item = new CacheItem(); $item->key = $key; - $item->value = $value; + $item->value = $v = $value; $item->isHit = $isHit; + // Detect wrapped values that encode for their expiry and creation duration + // For compactness, these values are packed in the key of an array using + // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F + if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) { + $item->value = $v[$k]; + $v = unpack('Ve/Nc', substr($k, 1, -1)); + $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET; + $item->metadata[CacheItem::METADATA_CTIME] = $v['c']; + } return $item; }, null, CacheItem::class ); - $getId = function ($key) { return $this->getId((string) $key); }; - $this->mergeByLifetime = \Closure::bind( - static function ($deferred, $namespace, &$expiredIds) use ($getId, $defaultLifetime) { + self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind( + static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) { $byLifetime = []; - $now = time(); + $now = microtime(true); $expiredIds = []; foreach ($deferred as $key => $item) { + $key = (string) $key; if (null === $item->expiry) { - $byLifetime[0 < $defaultLifetime ? $defaultLifetime : 0][$getId($key)] = $item->value; - } elseif (0 === $item->expiry) { - $byLifetime[0][$getId($key)] = $item->value; - } elseif ($item->expiry > $now) { - $byLifetime[$item->expiry - $now][$getId($key)] = $item->value; - } else { + $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0; + } elseif (!$item->expiry) { + $ttl = 0; + } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) { $expiredIds[] = $getId($key); + continue; } + if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) { + unset($metadata[CacheItem::METADATA_TAGS]); + } + // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators + $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value; } return $byLifetime; @@ -86,150 +95,72 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface } /** - * @param string $namespace - * @param int $defaultLifetime - * @param string $version - * @param string $directory + * Returns the best possible adapter that your runtime supports. + * + * Using ApcuAdapter makes system caches compatible with read-only filesystems. * * @return AdapterInterface */ - public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) + public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null) { - if (null === self::$apcuSupported) { - self::$apcuSupported = ApcuAdapter::isSupported(); + $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true); + if (null !== $logger) { + $opcache->setLogger($logger); } - 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); - } - + if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) { return $opcache; } - $fs = new FilesystemAdapter($namespace, $defaultLifetime, $directory); - if (null !== $logger) { - $fs->setLogger($logger); - } - if (!self::$apcuSupported || (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) { - return $fs; + if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) { + return $opcache; } - $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version); + $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version); if (null !== $logger) { $apcu->setLogger($logger); } - return new ChainAdapter([$apcu, $fs]); + return new ChainAdapter([$apcu, $opcache]); } - public static function createConnection($dsn, array $options = []) + public static function createConnection(string $dsn, array $options = []) { - if (!\is_string($dsn)) { - throw new InvalidArgumentException(sprintf('The "%s()" method expect argument #1 to be string, "%s" given.', __METHOD__, \gettype($dsn))); - } - if (0 === strpos($dsn, 'redis://')) { + if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) { return RedisAdapter::createConnection($dsn, $options); } - if (0 === strpos($dsn, 'memcached://')) { + if (str_starts_with($dsn, 'memcached:')) { return MemcachedAdapter::createConnection($dsn, $options); } + if (0 === strpos($dsn, 'couchbase:')) { + if (CouchbaseBucketAdapter::isSupported()) { + return CouchbaseBucketAdapter::createConnection($dsn, $options); + } + + return CouchbaseCollectionAdapter::createConnection($dsn, $options); + } throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn)); } /** * {@inheritdoc} - */ - public function getItem($key) - { - if ($this->deferred) { - $this->commit(); - } - $id = $this->getId($key); - - $f = $this->createCacheItem; - $isHit = false; - $value = null; - - try { - foreach ($this->doFetch([$id]) as $value) { - $isHit = true; - } - } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]); - } - - return $f($key, $value, $isHit); - } - - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []) - { - if ($this->deferred) { - $this->commit(); - } - $ids = []; - - foreach ($keys as $key) { - $ids[] = $this->getId($key); - } - try { - $items = $this->doFetch($ids); - } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]); - $items = []; - } - $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} + * + * @return bool */ public function commit() { $ok = true; - $byLifetime = $this->mergeByLifetime; - $byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds); + $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime); $retry = $this->deferred = []; if ($expiredIds) { - $this->doDelete($expiredIds); + try { + $this->doDelete($expiredIds); + } catch (\Exception $e) { + $ok = false; + CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]); + } } foreach ($byLifetime as $lifetime => $values) { try { @@ -243,8 +174,9 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface foreach (\is_array($e) ? $e : array_keys($values) as $id) { $ok = false; $v = $values[$id]; - $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]); + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } } else { foreach ($values as $id => $v) { @@ -265,50 +197,12 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface continue; } $ok = false; - $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]); + $type = get_debug_type($v); + $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.'); + CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]); } } return $ok; } - - public function __sleep() - { - throw new \BadMethodCallException('Cannot serialize '.__CLASS__); - } - - public function __wakeup() - { - throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); - } - - 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) { - CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]); - } - - foreach ($keys as $key) { - yield $key => $f($key, null, false); - } - } } diff --git a/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php new file mode 100644 index 000000000..a384b16ad --- /dev/null +++ b/lib/symfony/cache/Adapter/AbstractTagAwareAdapter.php @@ -0,0 +1,330 @@ + + * + * 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\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\AbstractAdapterTrait; +use Symfony\Component\Cache\Traits\ContractsTrait; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +/** + * Abstract for native TagAware adapters. + * + * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids + * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate(). + * + * @author Nicolas Grekas
+ * @author André Rømcke
+ */
class ApcuAdapter extends AbstractAdapter
{
- use ApcuTrait;
+ private $marshaller;
/**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $version
- *
* @throws CacheException if APCu is not enabled
*/
- public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null, MarshallerInterface $marshaller = null)
{
- $this->init($namespace, $defaultLifetime, $version);
+ if (!static::isSupported()) {
+ throw new CacheException('APCu is not enabled.');
+ }
+ if ('cli' === \PHP_SAPI) {
+ ini_set('apc.use_request_time', 0);
+ }
+ parent::__construct($namespace, $defaultLifetime);
+
+ if (null !== $version) {
+ CacheItem::validateKey($version);
+
+ if (!apcu_exists($version.'@'.$namespace)) {
+ $this->doClear($namespace);
+ apcu_add($version.'@'.$namespace, null);
+ }
+ }
+
+ $this->marshaller = $marshaller;
+ }
+
+ public static function isSupported()
+ {
+ return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+ try {
+ $values = [];
+ $ids = array_flip($ids);
+ foreach (apcu_fetch(array_keys($ids), $ok) ?: [] as $k => $v) {
+ if (!isset($ids[$k])) {
+ // work around https://github.com/krakjoe/apcu/issues/247
+ $k = key($ids);
+ }
+ unset($ids[$k]);
+
+ if (null !== $v || $ok) {
+ $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v;
+ }
+ }
+
+ return $values;
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ return apcu_exists($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ return isset($namespace[0]) && class_exists(\APCuIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))
+ ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY))
+ : apcu_clear_cache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ foreach ($ids as $id) {
+ apcu_delete($id);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) {
+ return $failed;
+ }
+
+ try {
+ if (false === $failures = apcu_store($values, null, $lifetime)) {
+ $failures = $values;
+ }
+
+ return array_keys($failures);
+ } catch (\Throwable $e) {
+ if (1 === \count($values)) {
+ // Workaround https://github.com/krakjoe/apcu/issues/170
+ apcu_delete(array_key_first($values));
+ }
+
+ throw $e;
+ }
}
}
diff --git a/lib/symfony/cache/Adapter/ArrayAdapter.php b/lib/symfony/cache/Adapter/ArrayAdapter.php
index 33f55a869..bd5ec9ec9 100644
--- a/lib/symfony/cache/Adapter/ArrayAdapter.php
+++ b/lib/symfony/cache/Adapter/ArrayAdapter.php
@@ -13,29 +13,50 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
-use Symfony\Component\Cache\Traits\ArrayTrait;
+use Symfony\Contracts\Cache\CacheInterface;
/**
+ * An in-memory cache storage.
+ *
+ * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items.
+ *
* @author Nicolas Grekas
*/
-class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
+class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
- use ArrayTrait;
+ use LoggerAwareTrait;
- private $createCacheItem;
+ private $storeSerialized;
+ private $values = [];
+ private $expiries = [];
private $defaultLifetime;
+ private $maxLifetime;
+ private $maxItems;
+
+ private static $createCacheItem;
/**
- * @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
- public function __construct($defaultLifetime = 0, $storeSerialized = true)
+ public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true, float $maxLifetime = 0, int $maxItems = 0)
{
+ if (0 > $maxLifetime) {
+ throw new InvalidArgumentException(sprintf('Argument $maxLifetime must be positive, %F passed.', $maxLifetime));
+ }
+
+ if (0 > $maxItems) {
+ throw new InvalidArgumentException(sprintf('Argument $maxItems must be a positive integer, %d passed.', $maxItems));
+ }
+
$this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
- $this->createCacheItem = \Closure::bind(
+ $this->maxLifetime = $maxLifetime;
+ $this->maxItems = $maxItems;
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
@@ -49,31 +70,70 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $item = $this->getItem($key);
+ $metadata = $item->getMetadata();
+
+ // ArrayAdapter works in memory, we don't care about stampede protection
+ if (\INF === $beta || !$item->isHit()) {
+ $save = true;
+ $this->save($item->set($callback($item, $save)));
+ }
+
+ return $item->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
+ if ($this->maxItems) {
+ // Move the item last in the storage
+ $value = $this->values[$key];
+ unset($this->values[$key]);
+ $this->values[$key] = $value;
+ }
+
+ return true;
+ }
+ \assert('' !== CacheItem::validateKey($key));
+
+ return isset($this->expiries[$key]) && !$this->deleteItem($key);
+ }
+
/**
* {@inheritdoc}
*/
public function getItem($key)
{
- $isHit = $this->hasItem($key);
- try {
- if (!$isHit) {
- $this->values[$key] = $value = null;
- } elseif (!$this->storeSerialized) {
- $value = $this->values[$key];
- } elseif ('b:0;' === $value = $this->values[$key]) {
- $value = false;
- } elseif (false === $value = unserialize($value)) {
- $this->values[$key] = $value = null;
- $isHit = false;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
- $this->values[$key] = $value = null;
- $isHit = false;
- }
- $f = $this->createCacheItem;
+ if (!$isHit = $this->hasItem($key)) {
+ $value = null;
- return $f($key, $value, $isHit);
+ if (!$this->maxItems) {
+ // Track misses in non-LRU mode only
+ $this->values[$key] = null;
+ }
+ } else {
+ $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
+ }
+
+ return (self::$createCacheItem)($key, $value, $isHit);
}
/**
@@ -81,15 +141,28 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
*/
public function getItems(array $keys = [])
{
- foreach ($keys as $key) {
- CacheItem::validateKey($key);
- }
+ \assert(self::validateKeys($keys));
- return $this->generateItems($keys, time(), $this->createCacheItem);
+ return $this->generateItems($keys, microtime(true), self::$createCacheItem);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ \assert('' !== CacheItem::validateKey($key));
+ unset($this->values[$key], $this->expiries[$key]);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
@@ -102,6 +175,8 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
@@ -113,37 +188,50 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
- if (0 === $expiry) {
- $expiry = \PHP_INT_MAX;
- }
+ $now = microtime(true);
- if (null !== $expiry && $expiry <= time()) {
- $this->deleteItem($key);
+ if (null !== $expiry) {
+ if (!$expiry) {
+ $expiry = \PHP_INT_MAX;
+ } elseif ($expiry <= $now) {
+ $this->deleteItem($key);
- return true;
- }
- if ($this->storeSerialized) {
- try {
- $value = serialize($value);
- } catch (\Exception $e) {
- $type = \is_object($value) ? \get_class($value) : \gettype($value);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
-
- return false;
+ return true;
}
}
+ if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+ return false;
+ }
if (null === $expiry && 0 < $this->defaultLifetime) {
- $expiry = time() + $this->defaultLifetime;
+ $expiry = $this->defaultLifetime;
+ $expiry = $now + ($expiry > ($this->maxLifetime ?: $expiry) ? $this->maxLifetime : $expiry);
+ } elseif ($this->maxLifetime && (null === $expiry || $expiry > $now + $this->maxLifetime)) {
+ $expiry = $now + $this->maxLifetime;
+ }
+
+ if ($this->maxItems) {
+ unset($this->values[$key]);
+
+ // Iterate items and vacuum expired ones while we are at it
+ foreach ($this->values as $k => $v) {
+ if ($this->expiries[$k] > $now && \count($this->values) < $this->maxItems) {
+ break;
+ }
+
+ unset($this->values[$k], $this->expiries[$k]);
+ }
}
$this->values[$key] = $value;
- $this->expiries[$key] = null !== $expiry ? $expiry : \PHP_INT_MAX;
+ $this->expiries[$key] = $expiry ?? \PHP_INT_MAX;
return true;
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@@ -152,9 +240,165 @@ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, Resettable
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
return true;
}
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '')
+ {
+ if ('' !== $prefix) {
+ $now = microtime(true);
+
+ foreach ($this->values as $key => $value) {
+ if (!isset($this->expiries[$key]) || $this->expiries[$key] <= $now || 0 === strpos($key, $prefix)) {
+ unset($this->values[$key], $this->expiries[$key]);
+ }
+ }
+
+ if ($this->values) {
+ return true;
+ }
+ }
+
+ $this->values = $this->expiries = [];
+
+ return true;
+ }
+
+ /**
+ * Returns all cached values, with cache miss as null.
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ if (!$this->storeSerialized) {
+ return $this->values;
+ }
+
+ $values = $this->values;
+ foreach ($values as $k => $v) {
+ if (null === $v || 'N;' === $v) {
+ continue;
+ }
+ if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
+ $values[$k] = serialize($v);
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ $this->clear();
+ }
+
+ private function generateItems(array $keys, float $now, \Closure $f): \Generator
+ {
+ foreach ($keys as $i => $key) {
+ if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
+ $value = null;
+
+ if (!$this->maxItems) {
+ // Track misses in non-LRU mode only
+ $this->values[$key] = null;
+ }
+ } else {
+ if ($this->maxItems) {
+ // Move the item last in the storage
+ $value = $this->values[$key];
+ unset($this->values[$key]);
+ $this->values[$key] = $value;
+ }
+
+ $value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
+ }
+ unset($keys[$i]);
+
+ yield $key => $f($key, $value, $isHit);
+ }
+
+ foreach ($keys as $key) {
+ yield $key => $f($key, null, false);
+ }
+ }
+
+ private function freeze($value, string $key)
+ {
+ if (null === $value) {
+ return 'N;';
+ }
+ if (\is_string($value)) {
+ // Serialize strings if they could be confused with serialized objects or arrays
+ if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
+ return serialize($value);
+ }
+ } elseif (!is_scalar($value)) {
+ try {
+ $serialized = serialize($value);
+ } catch (\Exception $e) {
+ unset($this->values[$key]);
+ $type = get_debug_type($value);
+ $message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+
+ return;
+ }
+ // Keep value serialized if it contains any objects or any internal references
+ if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
+ return $serialized;
+ }
+ }
+
+ return $value;
+ }
+
+ private function unfreeze(string $key, bool &$isHit)
+ {
+ if ('N;' === $value = $this->values[$key]) {
+ return null;
+ }
+ if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ try {
+ $value = unserialize($value);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ $value = false;
+ }
+ if (false === $value) {
+ $value = null;
+ $isHit = false;
+
+ if (!$this->maxItems) {
+ $this->values[$key] = null;
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ private function validateKeys(array $keys): bool
+ {
+ foreach ($keys as $key) {
+ if (!\is_string($key) || !isset($this->expiries[$key])) {
+ CacheItem::validateKey($key);
+ }
+ }
+
+ return true;
+ }
}
diff --git a/lib/symfony/cache/Adapter/ChainAdapter.php b/lib/symfony/cache/Adapter/ChainAdapter.php
index fdb28846f..568069763 100644
--- a/lib/symfony/cache/Adapter/ChainAdapter.php
+++ b/lib/symfony/cache/Adapter/ChainAdapter.php
@@ -17,6 +17,9 @@ use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
/**
* Chains several adapters together.
@@ -26,17 +29,21 @@ use Symfony\Component\Cache\ResettableInterface;
*
* @author Kévin Dunglas
+ *
+ * @deprecated Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead
+ */
class DoctrineAdapter extends AbstractAdapter
{
- use DoctrineTrait;
+ private $provider;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
+ public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
{
+ trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "%s" instead.', __CLASS__, CacheAdapter::class);
+
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
$provider->setNamespace($namespace);
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ parent::reset();
+ $this->provider->setNamespace($this->provider->getNamespace());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
+ try {
+ return $this->provider->fetchMultiple($ids);
+ } catch (\Error $e) {
+ $trace = $e->getTrace();
+
+ if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
+ switch ($trace[0]['function']) {
+ case 'unserialize':
+ case 'apcu_fetch':
+ case 'apc_fetch':
+ throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
+ }
+ }
+
+ throw $e;
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ return $this->provider->contains($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ $namespace = $this->provider->getNamespace();
+
+ return isset($namespace[0])
+ ? $this->provider->deleteAll()
+ : $this->provider->flushAll();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $ok = true;
+ foreach ($ids as $id) {
+ $ok = $this->provider->delete($id) && $ok;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ return $this->provider->saveMultiple($values, $lifetime);
+ }
}
diff --git a/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php b/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php
new file mode 100644
index 000000000..73f0ea6bc
--- /dev/null
+++ b/lib/symfony/cache/Adapter/DoctrineDbalAdapter.php
@@ -0,0 +1,397 @@
+
+ *
+ * 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 Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
+use Doctrine\DBAL\DriverManager;
+use Doctrine\DBAL\Exception as DBALException;
+use Doctrine\DBAL\Exception\TableNotFoundException;
+use Doctrine\DBAL\ParameterType;
+use Doctrine\DBAL\Schema\Schema;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+
+class DoctrineDbalAdapter extends AbstractAdapter implements PruneableInterface
+{
+ protected $maxIdLength = 255;
+
+ private $marshaller;
+ private $conn;
+ private $platformName;
+ private $serverVersion;
+ private $table = 'cache_items';
+ private $idCol = 'item_id';
+ private $dataCol = 'item_data';
+ private $lifetimeCol = 'item_lifetime';
+ private $timeCol = 'item_time';
+ private $namespace;
+
+ /**
+ * You can either pass an existing database Doctrine DBAL Connection or
+ * a DSN string that will be used to connect to the database.
+ *
+ * The cache table is created automatically when possible.
+ * Otherwise, use the createTable() method.
+ *
+ * List of available options:
+ * * db_table: The name of the table [default: cache_items]
+ * * db_id_col: The column where to store the cache id [default: item_id]
+ * * db_data_col: The column where to store the cache data [default: item_data]
+ * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
+ * * db_time_col: The column where to store the timestamp [default: item_time]
+ *
+ * @param Connection|string $connOrDsn
+ *
+ * @throws InvalidArgumentException When namespace contains invalid characters
+ */
+ public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
+ {
+ if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
+ throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
+ }
+
+ if ($connOrDsn instanceof Connection) {
+ $this->conn = $connOrDsn;
+ } elseif (\is_string($connOrDsn)) {
+ if (!class_exists(DriverManager::class)) {
+ throw new InvalidArgumentException(sprintf('Failed to parse the DSN "%s". Try running "composer require doctrine/dbal".', $connOrDsn));
+ }
+ $this->conn = DriverManager::getConnection(['url' => $connOrDsn]);
+ } else {
+ throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be "%s" or string, "%s" given.', __METHOD__, Connection::class, get_debug_type($connOrDsn)));
+ }
+
+ $this->table = $options['db_table'] ?? $this->table;
+ $this->idCol = $options['db_id_col'] ?? $this->idCol;
+ $this->dataCol = $options['db_data_col'] ?? $this->dataCol;
+ $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol;
+ $this->timeCol = $options['db_time_col'] ?? $this->timeCol;
+ $this->namespace = $namespace;
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+
+ parent::__construct($namespace, $defaultLifetime);
+ }
+
+ /**
+ * Creates the table to store cache items which can be called once for setup.
+ *
+ * Cache ID are saved in a column of maximum length 255. Cache data is
+ * saved in a BLOB.
+ *
+ * @throws DBALException When the table already exists
+ */
+ public function createTable()
+ {
+ $schema = new Schema();
+ $this->addTableToSchema($schema);
+
+ foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) {
+ $this->conn->executeStatement($sql);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function configureSchema(Schema $schema, Connection $forConnection): void
+ {
+ // only update the schema for this connection
+ if ($forConnection !== $this->conn) {
+ return;
+ }
+
+ if ($schema->hasTable($this->table)) {
+ return;
+ }
+
+ $this->addTableToSchema($schema);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune(): bool
+ {
+ $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ?";
+ $params = [time()];
+ $paramTypes = [ParameterType::INTEGER];
+
+ if ('' !== $this->namespace) {
+ $deleteSql .= " AND $this->idCol LIKE ?";
+ $params[] = sprintf('%s%%', $this->namespace);
+ $paramTypes[] = ParameterType::STRING;
+ }
+
+ try {
+ $this->conn->executeStatement($deleteSql, $params, $paramTypes);
+ } catch (TableNotFoundException $e) {
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids): iterable
+ {
+ $now = time();
+ $expired = [];
+
+ $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN (?)";
+ $result = $this->conn->executeQuery($sql, [
+ $now,
+ $ids,
+ ], [
+ ParameterType::INTEGER,
+ Connection::PARAM_STR_ARRAY,
+ ])->iterateNumeric();
+
+ foreach ($result as $row) {
+ if (null === $row[1]) {
+ $expired[] = $row[0];
+ } else {
+ yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
+ }
+ }
+
+ if ($expired) {
+ $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN (?)";
+ $this->conn->executeStatement($sql, [
+ $now,
+ $expired,
+ ], [
+ ParameterType::INTEGER,
+ Connection::PARAM_STR_ARRAY,
+ ]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id): bool
+ {
+ $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = ? AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ?)";
+ $result = $this->conn->executeQuery($sql, [
+ $id,
+ time(),
+ ], [
+ ParameterType::STRING,
+ ParameterType::INTEGER,
+ ]);
+
+ return (bool) $result->fetchOne();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace): bool
+ {
+ if ('' === $namespace) {
+ if ('sqlite' === $this->getPlatformName()) {
+ $sql = "DELETE FROM $this->table";
+ } else {
+ $sql = "TRUNCATE TABLE $this->table";
+ }
+ } else {
+ $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
+ }
+
+ try {
+ $this->conn->executeStatement($sql);
+ } catch (TableNotFoundException $e) {
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids): bool
+ {
+ $sql = "DELETE FROM $this->table WHERE $this->idCol IN (?)";
+ try {
+ $this->conn->executeStatement($sql, [array_values($ids)], [Connection::PARAM_STR_ARRAY]);
+ } catch (TableNotFoundException $e) {
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ $platformName = $this->getPlatformName();
+ $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?)";
+
+ switch (true) {
+ case 'mysql' === $platformName:
+ $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
+ break;
+ case 'oci' === $platformName:
+ // DUAL is Oracle specific dummy table
+ $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
+ "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+ "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
+ break;
+ case 'sqlsrv' === $platformName && version_compare($this->getServerVersion(), '10', '>='):
+ // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
+ // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
+ $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
+ "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
+ "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
+ break;
+ case 'sqlite' === $platformName:
+ $sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
+ break;
+ case 'pgsql' === $platformName && version_compare($this->getServerVersion(), '9.5', '>='):
+ $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
+ break;
+ default:
+ $platformName = null;
+ $sql = "UPDATE $this->table SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ? WHERE $this->idCol = ?";
+ break;
+ }
+
+ $now = time();
+ $lifetime = $lifetime ?: null;
+ try {
+ $stmt = $this->conn->prepare($sql);
+ } catch (TableNotFoundException $e) {
+ if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt = $this->conn->prepare($sql);
+ }
+
+ // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution.
+ if ('sqlsrv' === $platformName || 'oci' === $platformName) {
+ $stmt->bindParam(1, $id);
+ $stmt->bindParam(2, $id);
+ $stmt->bindParam(3, $data, ParameterType::LARGE_OBJECT);
+ $stmt->bindValue(4, $lifetime, ParameterType::INTEGER);
+ $stmt->bindValue(5, $now, ParameterType::INTEGER);
+ $stmt->bindParam(6, $data, ParameterType::LARGE_OBJECT);
+ $stmt->bindValue(7, $lifetime, ParameterType::INTEGER);
+ $stmt->bindValue(8, $now, ParameterType::INTEGER);
+ } elseif (null !== $platformName) {
+ $stmt->bindParam(1, $id);
+ $stmt->bindParam(2, $data, ParameterType::LARGE_OBJECT);
+ $stmt->bindValue(3, $lifetime, ParameterType::INTEGER);
+ $stmt->bindValue(4, $now, ParameterType::INTEGER);
+ } else {
+ $stmt->bindParam(1, $data, ParameterType::LARGE_OBJECT);
+ $stmt->bindValue(2, $lifetime, ParameterType::INTEGER);
+ $stmt->bindValue(3, $now, ParameterType::INTEGER);
+ $stmt->bindParam(4, $id);
+
+ $insertStmt = $this->conn->prepare($insertSql);
+ $insertStmt->bindParam(1, $id);
+ $insertStmt->bindParam(2, $data, ParameterType::LARGE_OBJECT);
+ $insertStmt->bindValue(3, $lifetime, ParameterType::INTEGER);
+ $insertStmt->bindValue(4, $now, ParameterType::INTEGER);
+ }
+
+ foreach ($values as $id => $data) {
+ try {
+ $rowCount = $stmt->executeStatement();
+ } catch (TableNotFoundException $e) {
+ if (!$this->conn->isTransactionActive() || \in_array($platformName, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $rowCount = $stmt->executeStatement();
+ }
+ if (null === $platformName && 0 === $rowCount) {
+ try {
+ $insertStmt->executeStatement();
+ } catch (DBALException $e) {
+ // A concurrent write won, let it be
+ }
+ }
+ }
+
+ return $failed;
+ }
+
+ private function getPlatformName(): string
+ {
+ if (isset($this->platformName)) {
+ return $this->platformName;
+ }
+
+ $platform = $this->conn->getDatabasePlatform();
+
+ switch (true) {
+ case $platform instanceof \Doctrine\DBAL\Platforms\MySQLPlatform:
+ case $platform instanceof \Doctrine\DBAL\Platforms\MySQL57Platform:
+ return $this->platformName = 'mysql';
+
+ case $platform instanceof \Doctrine\DBAL\Platforms\SqlitePlatform:
+ return $this->platformName = 'sqlite';
+
+ case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform:
+ case $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQL94Platform:
+ return $this->platformName = 'pgsql';
+
+ case $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform:
+ return $this->platformName = 'oci';
+
+ case $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform:
+ case $platform instanceof \Doctrine\DBAL\Platforms\SQLServer2012Platform:
+ return $this->platformName = 'sqlsrv';
+
+ default:
+ return $this->platformName = \get_class($platform);
+ }
+ }
+
+ private function getServerVersion(): string
+ {
+ if (isset($this->serverVersion)) {
+ return $this->serverVersion;
+ }
+
+ $conn = $this->conn->getWrappedConnection();
+ if ($conn instanceof ServerInfoAwareConnection) {
+ return $this->serverVersion = $conn->getServerVersion();
+ }
+
+ return $this->serverVersion = '0';
+ }
+
+ private function addTableToSchema(Schema $schema): void
+ {
+ $types = [
+ 'mysql' => 'binary',
+ 'sqlite' => 'text',
+ ];
+
+ $table = $schema->createTable($this->table);
+ $table->addColumn($this->idCol, $types[$this->getPlatformName()] ?? 'string', ['length' => 255]);
+ $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
+ $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
+ $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
+ $table->setPrimaryKey([$this->idCol]);
+ }
+}
diff --git a/lib/symfony/cache/Adapter/FilesystemAdapter.php b/lib/symfony/cache/Adapter/FilesystemAdapter.php
index d071964ec..7185dd487 100644
--- a/lib/symfony/cache/Adapter/FilesystemAdapter.php
+++ b/lib/symfony/cache/Adapter/FilesystemAdapter.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Cache\Adapter;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
@@ -18,13 +20,9 @@ class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
{
use FilesystemTrait;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
{
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
diff --git a/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
new file mode 100644
index 000000000..afde84375
--- /dev/null
+++ b/lib/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
@@ -0,0 +1,239 @@
+
+ *
+ * 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 Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
+ *
+ * @author Nicolas Grekas
+ * @author André Rømcke
+ */
class MemcachedAdapter extends AbstractAdapter
{
- use MemcachedTrait;
+ /**
+ * We are replacing characters that are illegal in Memcached keys with reserved characters from
+ * {@see \Symfony\Contracts\Cache\ItemInterface::RESERVED_CHARACTERS} that are legal in Memcached.
+ * Note: don’t use {@see \Symfony\Component\Cache\Adapter\AbstractAdapter::NS_SEPARATOR}.
+ */
+ private const RESERVED_MEMCACHED = " \n\r\t\v\f\0";
+ private const RESERVED_PSR6 = '@()\{}/';
protected $maxIdLength = 250;
+ private const DEFAULT_CLIENT_OPTIONS = [
+ 'persistent_id' => null,
+ 'username' => null,
+ 'password' => null,
+ \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
+ ];
+
+ private $marshaller;
+ private $client;
+ private $lazyClient;
+
/**
* Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged.
* Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that:
@@ -29,8 +53,301 @@ class MemcachedAdapter extends AbstractAdapter
*
* Using a MemcachedAdapter as a pure items store is fine.
*/
- public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
+ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
- $this->init($client, $namespace, $defaultLifetime);
+ if (!static::isSupported()) {
+ throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.');
+ }
+ if ('Memcached' === \get_class($client)) {
+ $opt = $client->getOption(\Memcached::OPT_SERIALIZER);
+ if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
+ throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+ }
+ $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
+ $this->client = $client;
+ } else {
+ $this->lazyClient = $client;
+ }
+
+ parent::__construct($namespace, $defaultLifetime);
+ $this->enableVersioning();
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ public static function isSupported()
+ {
+ return \extension_loaded('memcached') && version_compare(phpversion('memcached'), \PHP_VERSION_ID >= 80100 ? '3.1.6' : '2.2.0', '>=');
+ }
+
+ /**
+ * Creates a Memcached instance.
+ *
+ * By default, the binary protocol, no block, and libketama compatible options are enabled.
+ *
+ * Examples for servers:
+ * - 'memcached://user:pass@localhost?weight=33'
+ * - [['localhost', 11211, 33]]
+ *
+ * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
+ *
+ * @return \Memcached
+ *
+ * @throws \ErrorException When invalid options or servers are provided
+ */
+ public static function createConnection($servers, array $options = [])
+ {
+ if (\is_string($servers)) {
+ $servers = [$servers];
+ } elseif (!\is_array($servers)) {
+ throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', get_debug_type($servers)));
+ }
+ if (!static::isSupported()) {
+ throw new CacheException('Memcached '.(\PHP_VERSION_ID >= 80100 ? '> 3.1.5' : '>= 2.2.0').' is required.');
+ }
+ set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
+ try {
+ $options += static::DEFAULT_CLIENT_OPTIONS;
+ $client = new \Memcached($options['persistent_id']);
+ $username = $options['username'];
+ $password = $options['password'];
+
+ // parse any DSN in $servers
+ foreach ($servers as $i => $dsn) {
+ if (\is_array($dsn)) {
+ continue;
+ }
+ if (!str_starts_with($dsn, 'memcached:')) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached:".', $dsn));
+ }
+ $params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
+ if (!empty($m[2])) {
+ [$username, $password] = explode(':', $m[2], 2) + [1 => null];
+ }
+
+ return 'file:'.($m[1] ?? '');
+ }, $dsn);
+ if (false === $params = parse_url($params)) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
+ }
+ $query = $hosts = [];
+ if (isset($params['query'])) {
+ parse_str($params['query'], $query);
+
+ if (isset($query['host'])) {
+ if (!\is_array($hosts = $query['host'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
+ }
+ foreach ($hosts as $host => $weight) {
+ if (false === $port = strrpos($host, ':')) {
+ $hosts[$host] = [$host, 11211, (int) $weight];
+ } else {
+ $hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight];
+ }
+ }
+ $hosts = array_values($hosts);
+ unset($query['host']);
+ }
+ if ($hosts && !isset($params['host']) && !isset($params['path'])) {
+ unset($servers[$i]);
+ $servers = array_merge($servers, $hosts);
+ continue;
+ }
+ }
+ if (!isset($params['host']) && !isset($params['path'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
+ }
+ if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
+ $params['weight'] = $m[1];
+ $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+ }
+ $params += [
+ 'host' => $params['host'] ?? $params['path'],
+ 'port' => isset($params['host']) ? 11211 : null,
+ 'weight' => 0,
+ ];
+ if ($query) {
+ $params += $query;
+ $options = $query + $options;
+ }
+
+ $servers[$i] = [$params['host'], $params['port'], $params['weight']];
+
+ if ($hosts) {
+ $servers = array_merge($servers, $hosts);
+ }
+ }
+
+ // set client's options
+ unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
+ $options = array_change_key_case($options, \CASE_UPPER);
+ $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
+ $client->setOption(\Memcached::OPT_NO_BLOCK, true);
+ $client->setOption(\Memcached::OPT_TCP_NODELAY, true);
+ if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
+ $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
+ }
+ foreach ($options as $name => $value) {
+ if (\is_int($name)) {
+ continue;
+ }
+ if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
+ $value = \constant('Memcached::'.$name.'_'.strtoupper($value));
+ }
+ unset($options[$name]);
+
+ if (\defined('Memcached::OPT_'.$name)) {
+ $options[\constant('Memcached::OPT_'.$name)] = $value;
+ }
+ }
+ $client->setOptions($options);
+
+ // set client's servers, taking care of persistent connections
+ if (!$client->isPristine()) {
+ $oldServers = [];
+ foreach ($client->getServerList() as $server) {
+ $oldServers[] = [$server['host'], $server['port']];
+ }
+
+ $newServers = [];
+ foreach ($servers as $server) {
+ if (1 < \count($server)) {
+ $server = array_values($server);
+ unset($server[2]);
+ $server[1] = (int) $server[1];
+ }
+ $newServers[] = $server;
+ }
+
+ if ($oldServers !== $newServers) {
+ $client->resetServerList();
+ $client->addServers($servers);
+ }
+ } else {
+ $client->addServers($servers);
+ }
+
+ if (null !== $username || null !== $password) {
+ if (!method_exists($client, 'setSaslAuthData')) {
+ trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
+ }
+ $client->setSaslAuthData($username, $password);
+ }
+
+ return $client;
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ if ($lifetime && $lifetime > 30 * 86400) {
+ $lifetime += time();
+ }
+
+ $encodedValues = [];
+ foreach ($values as $key => $value) {
+ $encodedValues[self::encodeKey($key)] = $value;
+ }
+
+ return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ try {
+ $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids);
+
+ $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
+
+ $result = [];
+ foreach ($encodedResult as $key => $value) {
+ $result[self::decodeKey($key)] = $this->marshaller->unmarshall($value);
+ }
+
+ return $result;
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ return false !== $this->getClient()->get(self::encodeKey($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $ok = true;
+ $encodedIds = array_map([__CLASS__, 'encodeKey'], $ids);
+ foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
+ if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
+ $ok = false;
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ return '' === $namespace && $this->getClient()->flush();
+ }
+
+ private function checkResultCode($result)
+ {
+ $code = $this->client->getResultCode();
+
+ if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
+ return $result;
+ }
+
+ throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage()));
+ }
+
+ private function getClient(): \Memcached
+ {
+ if ($this->client) {
+ return $this->client;
+ }
+
+ $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
+ if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
+ throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
+ }
+ if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
+ throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
+ }
+
+ return $this->client = $this->lazyClient;
+ }
+
+ private static function encodeKey(string $key): string
+ {
+ return strtr($key, self::RESERVED_MEMCACHED, self::RESERVED_PSR6);
+ }
+
+ private static function decodeKey(string $key): string
+ {
+ return strtr($key, self::RESERVED_PSR6, self::RESERVED_MEMCACHED);
}
}
diff --git a/lib/symfony/cache/Adapter/NullAdapter.php b/lib/symfony/cache/Adapter/NullAdapter.php
index c81a1cd64..15f7f8c45 100644
--- a/lib/symfony/cache/Adapter/NullAdapter.php
+++ b/lib/symfony/cache/Adapter/NullAdapter.php
@@ -13,37 +13,46 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
+use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Titouan Galopin
*/
-class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
- use PhpArrayTrait;
+ use ContractsTrait;
+ use ProxyTrait;
- private $createCacheItem;
+ private $file;
+ private $keys;
+ private $values;
+
+ private static $createCacheItem;
+ private static $valuesCache = [];
/**
* @param string $file The PHP file were values are cached
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
*/
- public function __construct($file, AdapterInterface $fallbackPool)
+ public function __construct(string $file, AdapterInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
- $this->createCacheItem = \Closure::bind(
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
@@ -56,26 +64,53 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
}
/**
- * This adapter should only be used on PHP 7.0+ to take advantage of how PHP
- * stores arrays in its latest versions. This factory method decorates the given
- * fallback pool with this adapter only if the current PHP version is supported.
+ * This adapter takes advantage of how PHP stores arrays in its latest versions.
*
* @param string $file The PHP file were values are cached
* @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
*
* @return CacheItemPoolInterface
*/
- public static function create($file, CacheItemPoolInterface $fallbackPool)
+ public static function create(string $file, CacheItemPoolInterface $fallbackPool)
{
- if (\PHP_VERSION_ID >= 70000) {
- if (!$fallbackPool instanceof AdapterInterface) {
- $fallbackPool = new ProxyAdapter($fallbackPool);
- }
-
- return new static($file, $fallbackPool);
+ if (!$fallbackPool instanceof AdapterInterface) {
+ $fallbackPool = new ProxyAdapter($fallbackPool);
}
- return $fallbackPool;
+ return new static($file, $fallbackPool);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (null === $this->values) {
+ $this->initialize();
+ }
+ if (!isset($this->keys[$key])) {
+ get_from_pool:
+ if ($this->pool instanceof CacheInterface) {
+ return $this->pool->get($key, $callback, $beta, $metadata);
+ }
+
+ return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
+ }
+ $value = $this->values[$this->keys[$key]];
+
+ if ('N;' === $value) {
+ return null;
+ }
+ try {
+ if ($value instanceof \Closure) {
+ return $value();
+ }
+ } catch (\Throwable $e) {
+ unset($this->keys[$key]);
+ goto get_from_pool;
+ }
+
+ return $value;
}
/**
@@ -84,36 +119,30 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
public function getItem($key)
{
if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
}
if (null === $this->values) {
$this->initialize();
}
- if (!isset($this->values[$key])) {
+ if (!isset($this->keys[$key])) {
return $this->pool->getItem($key);
}
- $value = $this->values[$key];
+ $value = $this->values[$this->keys[$key]];
$isHit = true;
if ('N;' === $value) {
$value = null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ } elseif ($value instanceof \Closure) {
try {
- $e = null;
- $value = unserialize($value);
- } catch (\Error $e) {
- } catch (\Exception $e) {
- }
- if (null !== $e) {
+ $value = $value();
+ } catch (\Throwable $e) {
$value = null;
$isHit = false;
}
}
- $f = $this->createCacheItem;
-
- return $f($key, $value, $isHit);
+ return (self::$createCacheItem)($key, $value, $isHit);
}
/**
@@ -123,7 +152,7 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
{
foreach ($keys as $key) {
if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
}
}
if (null === $this->values) {
@@ -135,36 +164,42 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
}
if (null === $this->values) {
$this->initialize();
}
- return isset($this->values[$key]) || $this->pool->hasItem($key);
+ return isset($this->keys[$key]) || $this->pool->hasItem($key);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
}
if (null === $this->values) {
$this->initialize();
}
- return !isset($this->values[$key]) && $this->pool->deleteItem($key);
+ return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
@@ -173,10 +208,10 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
foreach ($keys as $key) {
if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
}
- if (isset($this->values[$key])) {
+ if (isset($this->keys[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@@ -195,6 +230,8 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
@@ -202,11 +239,13 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
$this->initialize();
}
- return !isset($this->values[$item->getKey()]) && $this->pool->save($item);
+ return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@@ -214,11 +253,13 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
$this->initialize();
}
- return !isset($this->values[$item->getKey()]) && $this->pool->saveDeferred($item);
+ return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
@@ -226,25 +267,157 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
}
/**
- * @return \Generator
+ * {@inheritdoc}
+ *
+ * @return bool
*/
- private function generateItems(array $keys)
+ public function clear(string $prefix = '')
{
- $f = $this->createCacheItem;
+ $this->keys = $this->values = [];
+
+ $cleared = @unlink($this->file) || !file_exists($this->file);
+ unset(self::$valuesCache[$this->file]);
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($prefix) && $cleared;
+ }
+
+ return $this->pool->clear() && $cleared;
+ }
+
+ /**
+ * Store an array of cached values.
+ *
+ * @param array $values The cached values
+ *
+ * @return string[] A list of classes to preload on PHP 7.4+
+ */
+ public function warmUp(array $values)
+ {
+ if (file_exists($this->file)) {
+ if (!is_file($this->file)) {
+ throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file));
+ }
+
+ if (!is_writable($this->file)) {
+ throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file));
+ }
+ } else {
+ $directory = \dirname($this->file);
+
+ if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
+ throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory));
+ }
+
+ if (!is_writable($directory)) {
+ throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory));
+ }
+ }
+
+ $preload = [];
+ $dumpedValues = '';
+ $dumpedMap = [];
+ $dump = <<<'EOF'
+ $value) {
+ CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
+ $isStaticValue = true;
+
+ if (null === $value) {
+ $value = "'N;'";
+ } elseif (\is_object($value) || \is_array($value)) {
+ try {
+ $value = VarExporter::export($value, $isStaticValue, $preload);
+ } catch (\Exception $e) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)), 0, $e);
+ }
+ } elseif (\is_string($value)) {
+ // Wrap "N;" in a closure to not confuse it with an encoded `null`
+ if ('N;' === $value) {
+ $isStaticValue = false;
+ }
+ $value = var_export($value, true);
+ } elseif (!is_scalar($value)) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, get_debug_type($value)));
+ } else {
+ $value = var_export($value, true);
+ }
+
+ if (!$isStaticValue) {
+ $value = str_replace("\n", "\n ", $value);
+ $value = "static function () {\n return {$value};\n}";
+ }
+ $hash = hash('md5', $value);
+
+ if (null === $id = $dumpedMap[$hash] ?? null) {
+ $id = $dumpedMap[$hash] = \count($dumpedMap);
+ $dumpedValues .= "{$id} => {$value},\n";
+ }
+
+ $dump .= var_export($key, true)." => {$id},\n";
+ }
+
+ $dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
+
+ $tmpFile = uniqid($this->file, true);
+
+ file_put_contents($tmpFile, $dump);
+ @chmod($tmpFile, 0666 & ~umask());
+ unset($serialized, $value, $dump);
+
+ @rename($tmpFile, $this->file);
+ unset(self::$valuesCache[$this->file]);
+
+ $this->initialize();
+
+ return $preload;
+ }
+
+ /**
+ * Load the cache file.
+ */
+ private function initialize()
+ {
+ if (isset(self::$valuesCache[$this->file])) {
+ $values = self::$valuesCache[$this->file];
+ } elseif (!is_file($this->file)) {
+ $this->keys = $this->values = [];
+
+ return;
+ } else {
+ $values = self::$valuesCache[$this->file] = (include $this->file) ?: [[], []];
+ }
+
+ if (2 !== \count($values) || !isset($values[0], $values[1])) {
+ $this->keys = $this->values = [];
+ } else {
+ [$this->keys, $this->values] = $values;
+ }
+ }
+
+ private function generateItems(array $keys): \Generator
+ {
+ $f = self::$createCacheItem;
$fallbackKeys = [];
foreach ($keys as $key) {
- if (isset($this->values[$key])) {
- $value = $this->values[$key];
+ if (isset($this->keys[$key])) {
+ $value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
yield $key => $f($key, null, true);
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
+ } elseif ($value instanceof \Closure) {
try {
- yield $key => $f($key, unserialize($value), true);
- } catch (\Error $e) {
- yield $key => $f($key, null, false);
- } catch (\Exception $e) {
+ yield $key => $f($key, $value(), true);
+ } catch (\Throwable $e) {
yield $key => $f($key, null, false);
}
} else {
@@ -256,54 +429,7 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl
}
if ($fallbackKeys) {
- foreach ($this->pool->getItems($fallbackKeys) as $key => $item) {
- yield $key => $item;
- }
+ yield from $this->pool->getItems($fallbackKeys);
}
}
-
- /**
- * @throws \ReflectionException When $class is not found and is required
- *
- * @internal to be removed in Symfony 5.0
- */
- public static function throwOnRequiredClass($class)
- {
- $e = new \ReflectionException("Class $class does not exist");
- $trace = debug_backtrace();
- $autoloadFrame = [
- 'function' => 'spl_autoload_call',
- 'args' => [$class],
- ];
-
- if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) {
- $callerFrame = $trace[1];
- } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
- $callerFrame = $trace[++$i];
- } else {
- throw $e;
- }
-
- if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
- switch ($callerFrame['function']) {
- case 'get_class_methods':
- case 'get_class_vars':
- case 'get_parent_class':
- case 'is_a':
- case 'is_subclass_of':
- case 'class_exists':
- case 'class_implements':
- case 'class_parents':
- case 'trait_exists':
- case 'defined':
- case 'interface_exists':
- case 'method_exists':
- case 'property_exists':
- case 'is_callable':
- return;
- }
- }
-
- throw $e;
- }
}
diff --git a/lib/symfony/cache/Adapter/PhpFilesAdapter.php b/lib/symfony/cache/Adapter/PhpFilesAdapter.php
index b56143c2c..f5766c330 100644
--- a/lib/symfony/cache/Adapter/PhpFilesAdapter.php
+++ b/lib/symfony/cache/Adapter/PhpFilesAdapter.php
@@ -12,30 +12,319 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\Traits\PhpFilesTrait;
+use Symfony\Component\Cache\Traits\FilesystemCommonTrait;
+use Symfony\Component\VarExporter\VarExporter;
+/**
+ * @author Piotr Stankowski
+ * @author Rob Frawley 2nd
*/
-class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
+ use ContractsTrait;
use ProxyTrait;
- private $namespace;
+ private $namespace = '';
private $namespaceLen;
- private $createCacheItem;
private $poolHash;
private $defaultLifetime;
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(CacheItemPoolInterface $pool, $namespace = '', $defaultLifetime = 0)
+ private static $createCacheItem;
+ private static $setInnerItem;
+
+ public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
$this->pool = $pool;
$this->poolHash = $poolHash = spl_object_hash($pool);
- $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
+ if ('' !== $namespace) {
+ \assert('' !== CacheItem::validateKey($namespace));
+ $this->namespace = $namespace;
+ }
$this->namespaceLen = \strlen($namespace);
$this->defaultLifetime = $defaultLifetime;
- $this->createCacheItem = \Closure::bind(
- static function ($key, $innerItem) use ($poolHash) {
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $innerItem, $poolHash) {
$item = new CacheItem();
$item->key = $key;
- $item->poolHash = $poolHash;
- if (null !== $innerItem) {
- $item->value = $innerItem->get();
- $item->isHit = $innerItem->isHit();
- $item->innerItem = $innerItem;
- $innerItem->set(null);
+ if (null === $innerItem) {
+ return $item;
}
+ $item->value = $v = $innerItem->get();
+ $item->isHit = $innerItem->isHit();
+ $item->innerItem = $innerItem;
+ $item->poolHash = $poolHash;
+
+ // Detect wrapped values that encode for their expiry and creation duration
+ // For compactness, these values are packed in the key of an array using
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
+ $item->value = $v[$k];
+ $v = unpack('Ve/Nc', substr($k, 1, -1));
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ } elseif ($innerItem instanceof CacheItem) {
+ $item->metadata = $innerItem->metadata;
+ }
+ $innerItem->set(null);
+
return $item;
},
null,
CacheItem::class
);
+ self::$setInnerItem ?? self::$setInnerItem = \Closure::bind(
+ /**
+ * @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
+ */
+ static function (CacheItemInterface $innerItem, array $item) {
+ // Tags are stored separately, no need to account for them when considering this item's newly set metadata
+ if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ }
+ if ($metadata) {
+ // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
+ $item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
+ }
+ $innerItem->set($item["\0*\0value"]);
+ $innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6F', $item["\0*\0expiry"])) : null);
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (!$this->pool instanceof CacheInterface) {
+ return $this->doGet($this, $key, $callback, $beta, $metadata);
+ }
+
+ return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
+ $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash);
+ $item->set($value = $callback($item, $save));
+ (self::$setInnerItem)($innerItem, (array) $item);
+
+ return $value;
+ }, $beta, $metadata);
}
/**
@@ -67,10 +122,9 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
*/
public function getItem($key)
{
- $f = $this->createCacheItem;
$item = $this->pool->getItem($this->getId($key));
- return $f($key, $item);
+ return (self::$createCacheItem)($key, $item, $this->poolHash);
}
/**
@@ -89,6 +143,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
@@ -97,14 +153,22 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(string $prefix = '')
{
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($this->namespace.$prefix);
+ }
+
return $this->pool->clear();
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
@@ -113,6 +177,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
@@ -127,6 +193,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
@@ -135,6 +203,8 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@@ -143,21 +213,22 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
return $this->pool->commit();
}
- private function doSave(CacheItemInterface $item, $method)
+ private function doSave(CacheItemInterface $item, string $method)
{
if (!$item instanceof CacheItem) {
return false;
}
$item = (array) $item;
- $expiry = $item["\0*\0expiry"];
- if (null === $expiry && 0 < $this->defaultLifetime) {
- $expiry = time() + $this->defaultLifetime;
+ if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) {
+ $item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
}
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
@@ -165,34 +236,32 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn
} elseif ($this->pool instanceof AdapterInterface) {
// this is an optimization specific for AdapterInterface implementations
// so we can save a round-trip to the backend by just creating a new item
- $f = $this->createCacheItem;
- $innerItem = $f($this->namespace.$item["\0*\0key"], null);
+ $innerItem = (self::$createCacheItem)($this->namespace.$item["\0*\0key"], null, $this->poolHash);
} else {
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
}
- $innerItem->set($item["\0*\0value"]);
- $innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null);
+ (self::$setInnerItem)($innerItem, $item);
return $this->pool->$method($innerItem);
}
- private function generateItems($items)
+ private function generateItems(iterable $items): \Generator
{
- $f = $this->createCacheItem;
+ $f = self::$createCacheItem;
foreach ($items as $key => $item) {
if ($this->namespaceLen) {
$key = substr($key, $this->namespaceLen);
}
- yield $key => $f($key, $item);
+ yield $key => $f($key, $item, $this->poolHash);
}
}
- private function getId($key)
+ private function getId($key): string
{
- CacheItem::validateKey($key);
+ \assert('' !== CacheItem::validateKey($key));
return $this->namespace.$key;
}
diff --git a/lib/symfony/cache/Adapter/SimpleCacheAdapter.php b/lib/symfony/cache/Adapter/Psr16Adapter.php
similarity index 74%
rename from lib/symfony/cache/Adapter/SimpleCacheAdapter.php
rename to lib/symfony/cache/Adapter/Psr16Adapter.php
index d3d0ede64..a56aa3930 100644
--- a/lib/symfony/cache/Adapter/SimpleCacheAdapter.php
+++ b/lib/symfony/cache/Adapter/Psr16Adapter.php
@@ -13,23 +13,26 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
+ * Turns a PSR-16 cache into a PSR-6 one.
+ *
* @author Nicolas Grekas
*/
-class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
+class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
{
+ use ProxyTrait;
+
/**
* @internal
*/
- const NS_SEPARATOR = '_';
-
- use ProxyTrait;
+ protected const NS_SEPARATOR = '_';
private $miss;
- public function __construct(CacheInterface $pool, $namespace = '', $defaultLifetime = 0)
+ public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct($namespace, $defaultLifetime);
@@ -52,7 +55,7 @@ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
/**
* {@inheritdoc}
*/
- protected function doHave($id)
+ protected function doHave(string $id)
{
return $this->pool->has($id);
}
@@ -60,7 +63,7 @@ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
/**
* {@inheritdoc}
*/
- protected function doClear($namespace)
+ protected function doClear(string $namespace)
{
return $this->pool->clear();
}
@@ -76,7 +79,7 @@ class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
}
diff --git a/lib/symfony/cache/Adapter/RedisAdapter.php b/lib/symfony/cache/Adapter/RedisAdapter.php
index c1e17997f..eb5950e53 100644
--- a/lib/symfony/cache/Adapter/RedisAdapter.php
+++ b/lib/symfony/cache/Adapter/RedisAdapter.php
@@ -11,6 +11,9 @@
namespace Symfony\Component\Cache\Adapter;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
use Symfony\Component\Cache\Traits\RedisTrait;
class RedisAdapter extends AbstractAdapter
@@ -18,12 +21,12 @@ class RedisAdapter extends AbstractAdapter
use RedisTrait;
/**
- * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient The redis client
- * @param string $namespace The default namespace
- * @param int $defaultLifetime The default lifetime
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis The redis client
+ * @param string $namespace The default namespace
+ * @param int $defaultLifetime The default lifetime
*/
- public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
+ public function __construct($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
- $this->init($redisClient, $namespace, $defaultLifetime);
+ $this->init($redis, $namespace, $defaultLifetime, $marshaller);
}
}
diff --git a/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php b/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php
new file mode 100644
index 000000000..865491e19
--- /dev/null
+++ b/lib/symfony/cache/Adapter/RedisTagAwareAdapter.php
@@ -0,0 +1,325 @@
+
+ *
+ * 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 Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Connection\Aggregate\PredisCluster;
+use Predis\Connection\Aggregate\ReplicationInterface;
+use Predis\Response\ErrorInterface;
+use Predis\Response\Status;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Component\Cache\Marshaller\DeflateMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
+use Symfony\Component\Cache\Traits\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a Redis Set.
+ *
+ * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
+ * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
+ * relationship survives eviction (cache cleanup when Redis runs out of memory).
+ *
+ * Redis server 2.8+ with any `volatile-*` eviction policy, OR `noeviction` if you're sure memory will NEVER fill up
+ *
+ * Design limitations:
+ * - Max 4 billion cache keys per cache tag as limited by Redis Set datatype.
+ * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 4 billion cache items also.
+ *
+ * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
+ * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
+ *
+ * @author Nicolas Grekas
+ * @author André Rømcke
*/
-class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface
+class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface
{
- const TAGS_PREFIX = "\0tags\0";
-
+ use ContractsTrait;
+ use LoggerAwareTrait;
use ProxyTrait;
+ public const TAGS_PREFIX = "\0tags\0";
+
private $deferred = [];
- private $createCacheItem;
- private $setCacheItemTags;
- private $getTagsByKey;
- private $invalidateTags;
private $tags;
private $knownTagVersions = [];
private $knownTagVersionsTtl;
- public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15)
+ private static $createCacheItem;
+ private static $setCacheItemTags;
+ private static $getTagsByKey;
+ private static $saveTags;
+
+ public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
{
$this->pool = $itemsPool;
$this->tags = $tagsPool ?: $itemsPool;
$this->knownTagVersionsTtl = $knownTagVersionsTtl;
- $this->createCacheItem = \Closure::bind(
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
static function ($key, $value, CacheItem $protoItem) {
$item = new CacheItem();
$item->key = $key;
@@ -54,14 +61,15 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
null,
CacheItem::class
);
- $this->setCacheItemTags = \Closure::bind(
+ self::$setCacheItemTags ?? self::$setCacheItemTags = \Closure::bind(
static function (CacheItem $item, $key, array &$itemTags) {
+ $item->isTaggable = true;
if (!$item->isHit) {
return $item;
}
if (isset($itemTags[$key])) {
foreach ($itemTags[$key] as $tag => $version) {
- $item->prevTags[$tag] = $tag;
+ $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
}
unset($itemTags[$key]);
} else {
@@ -74,11 +82,12 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
null,
CacheItem::class
);
- $this->getTagsByKey = \Closure::bind(
+ self::$getTagsByKey ?? self::$getTagsByKey = \Closure::bind(
static function ($deferred) {
$tagsByKey = [];
foreach ($deferred as $key => $item) {
- $tagsByKey[$key] = $item->tags;
+ $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
+ $item->metadata = $item->newMetadata;
}
return $tagsByKey;
@@ -86,8 +95,10 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
null,
CacheItem::class
);
- $this->invalidateTags = \Closure::bind(
+ self::$saveTags ?? self::$saveTags = \Closure::bind(
static function (AdapterInterface $tagsAdapter, array $tags) {
+ ksort($tags);
+
foreach ($tags as $v) {
$v->expiry = 0;
$tagsAdapter->saveDeferred($v);
@@ -105,52 +116,27 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
*/
public function invalidateTags(array $tags)
{
- $ok = true;
- $tagsByKey = [];
- $invalidatedTags = [];
+ $ids = [];
foreach ($tags as $tag) {
- CacheItem::validateKey($tag);
- $invalidatedTags[$tag] = 0;
+ \assert('' !== CacheItem::validateKey($tag));
+ unset($this->knownTagVersions[$tag]);
+ $ids[] = $tag.static::TAGS_PREFIX;
}
- if ($this->deferred) {
- $items = $this->deferred;
- foreach ($items as $key => $item) {
- if (!$this->pool->saveDeferred($item)) {
- unset($this->deferred[$key]);
- $ok = false;
- }
- }
-
- $f = $this->getTagsByKey;
- $tagsByKey = $f($items);
- $this->deferred = [];
- }
-
- $tagVersions = $this->getTagVersions($tagsByKey, $invalidatedTags);
- $f = $this->createCacheItem;
-
- foreach ($tagsByKey as $key => $tags) {
- $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
- }
- $ok = $this->pool->commit() && $ok;
-
- if ($invalidatedTags) {
- $f = $this->invalidateTags;
- $ok = $f($this->tags, $invalidatedTags) && $ok;
- }
-
- return $ok;
+ return !$tags || $this->tags->deleteItems($ids);
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
- if ($this->deferred) {
+ if (\is_string($key) && isset($this->deferred[$key])) {
$this->commit();
}
+
if (!$this->pool->hasItem($key)) {
return false;
}
@@ -166,7 +152,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
}
foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
- if ($itemTags[$tag] !== $version && 1 !== $itemTags[$tag] - $version) {
+ if ($itemTags[$tag] !== $version) {
return false;
}
}
@@ -191,18 +177,21 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
*/
public function getItems(array $keys = [])
{
- if ($this->deferred) {
- $this->commit();
- }
$tagKeys = [];
+ $commit = false;
foreach ($keys as $key) {
if ('' !== $key && \is_string($key)) {
+ $commit = $commit || isset($this->deferred[$key]);
$key = static::TAGS_PREFIX.$key;
$tagKeys[$key] = $key;
}
}
+ if ($commit) {
+ $this->commit();
+ }
+
try {
$items = $this->pool->getItems($tagKeys + $keys);
} catch (InvalidArgumentException $e) {
@@ -216,16 +205,32 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(string $prefix = '')
{
- $this->deferred = [];
+ if ('' !== $prefix) {
+ foreach ($this->deferred as $key => $item) {
+ if (str_starts_with($key, $prefix)) {
+ unset($this->deferred[$key]);
+ }
+ }
+ } else {
+ $this->deferred = [];
+ }
+
+ if ($this->pool instanceof AdapterInterface) {
+ return $this->pool->clear($prefix);
+ }
return $this->pool->clear();
}
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
@@ -234,6 +239,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
@@ -248,6 +255,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
@@ -261,6 +270,8 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@@ -274,12 +285,40 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
- return $this->invalidateTags([]);
+ if (!$this->deferred) {
+ return true;
+ }
+
+ $ok = true;
+ foreach ($this->deferred as $key => $item) {
+ if (!$this->pool->saveDeferred($item)) {
+ unset($this->deferred[$key]);
+ $ok = false;
+ }
+ }
+
+ $items = $this->deferred;
+ $tagsByKey = (self::$getTagsByKey)($items);
+ $this->deferred = [];
+
+ $tagVersions = $this->getTagVersions($tagsByKey);
+ $f = self::$createCacheItem;
+
+ foreach ($tagsByKey as $key => $tags) {
+ $this->pool->saveDeferred($f(static::TAGS_PREFIX.$key, array_intersect_key($tagVersions, $tags), $items[$key]));
+ }
+
+ return $this->pool->commit() && $ok;
}
+ /**
+ * @return array
+ */
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
@@ -295,10 +334,10 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
$this->commit();
}
- private function generateItems($items, array $tagKeys)
+ private function generateItems(iterable $items, array $tagKeys): \Generator
{
$bufferedItems = $itemTags = [];
- $f = $this->setCacheItemTags;
+ $f = self::$setCacheItemTags;
foreach ($items as $key => $item) {
if (!$tagKeys) {
@@ -321,7 +360,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
foreach ($itemTags as $key => $tags) {
foreach ($tags as $tag => $version) {
- if ($tagVersions[$tag] !== $version && 1 !== $version - $tagVersions[$tag]) {
+ if ($tagVersions[$tag] !== $version) {
unset($itemTags[$key]);
continue 2;
}
@@ -337,42 +376,32 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
}
}
- private function getTagVersions(array $tagsByKey, array &$invalidatedTags = [])
+ private function getTagVersions(array $tagsByKey)
{
- $tagVersions = $invalidatedTags;
+ $tagVersions = [];
+ $fetchTagVersions = false;
foreach ($tagsByKey as $tags) {
$tagVersions += $tags;
+
+ foreach ($tags as $tag => $version) {
+ if ($tagVersions[$tag] !== $version) {
+ unset($this->knownTagVersions[$tag]);
+ }
+ }
}
if (!$tagVersions) {
return [];
}
- if (!$fetchTagVersions = 1 !== \func_num_args()) {
- foreach ($tagsByKey as $tags) {
- foreach ($tags as $tag => $version) {
- if ($tagVersions[$tag] > $version) {
- $tagVersions[$tag] = $version;
- }
- }
- }
- }
-
$now = microtime(true);
$tags = [];
foreach ($tagVersions as $tag => $version) {
$tags[$tag.static::TAGS_PREFIX] = $tag;
- if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) {
+ if ($fetchTagVersions || ($this->knownTagVersions[$tag][1] ?? null) !== $version || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
+ // reuse previously fetched tag versions up to the ttl
$fetchTagVersions = true;
- continue;
- }
- $version -= $this->knownTagVersions[$tag][1];
- if ((0 !== $version && 1 !== $version) || $now - $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
- // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
- $fetchTagVersions = true;
- } else {
- $this->knownTagVersions[$tag][1] += $version;
}
}
@@ -380,14 +409,20 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R
return $tagVersions;
}
+ $newTags = [];
+ $newVersion = null;
foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
- $tagVersions[$tag = $tags[$tag]] = $version->get() ?: 0;
- if (isset($invalidatedTags[$tag])) {
- $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
+ if (!$version->isHit()) {
+ $newTags[$tag] = $version->set($newVersion ?? $newVersion = random_int(\PHP_INT_MIN, \PHP_INT_MAX));
}
+ $tagVersions[$tag = $tags[$tag]] = $version->get();
$this->knownTagVersions[$tag] = [$now, $tagVersions[$tag]];
}
+ if ($newTags) {
+ (self::$saveTags)($this->tags, $newTags);
+ }
+
return $tagVersions;
}
}
diff --git a/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php b/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php
index 340048c10..afa18d3b5 100644
--- a/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php
+++ b/lib/symfony/cache/Adapter/TagAwareAdapterInterface.php
@@ -25,7 +25,7 @@ interface TagAwareAdapterInterface extends AdapterInterface
*
* @param string[] $tags An array of tags to invalidate
*
- * @return bool True on success
+ * @return bool
*
* @throws InvalidArgumentException When $tags is not valid
*/
diff --git a/lib/symfony/cache/Adapter/TraceableAdapter.php b/lib/symfony/cache/Adapter/TraceableAdapter.php
index cc855c132..4b06557f8 100644
--- a/lib/symfony/cache/Adapter/TraceableAdapter.php
+++ b/lib/symfony/cache/Adapter/TraceableAdapter.php
@@ -12,8 +12,11 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Service\ResetInterface;
/**
* An adapter that collects data about all cache calls.
@@ -22,7 +25,7 @@ use Symfony\Component\Cache\ResettableInterface;
* @author Tobias Nyholm
*/
-class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
+class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
protected $pool;
private $calls = [];
@@ -32,6 +35,38 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
$this->pool = $pool;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (!$this->pool instanceof CacheInterface) {
+ throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class));
+ }
+
+ $isHit = true;
+ $callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
+ $isHit = $item->isHit();
+
+ return $callback($item, $save);
+ };
+
+ $event = $this->start(__FUNCTION__);
+ try {
+ $value = $this->pool->get($key, $callback, $beta, $metadata);
+ $event->result[$key] = get_debug_type($value);
+ } finally {
+ $event->end = microtime(true);
+ }
+ if ($isHit) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+
+ return $value;
+ }
+
/**
* {@inheritdoc}
*/
@@ -54,6 +89,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function hasItem($key)
{
@@ -67,6 +104,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItem($key)
{
@@ -80,6 +119,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function save(CacheItemInterface $item)
{
@@ -93,6 +134,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function saveDeferred(CacheItemInterface $item)
{
@@ -132,11 +175,17 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
- public function clear()
+ public function clear(string $prefix = '')
{
$event = $this->start(__FUNCTION__);
try {
+ if ($this->pool instanceof AdapterInterface) {
+ return $event->result = $this->pool->clear($prefix);
+ }
+
return $event->result = $this->pool->clear();
} finally {
$event->end = microtime(true);
@@ -145,6 +194,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteItems(array $keys)
{
@@ -159,6 +210,8 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function commit()
{
@@ -191,13 +244,26 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
*/
public function reset()
{
- if ($this->pool instanceof ResettableInterface) {
+ if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
$this->clearCalls();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->deleteItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
public function getCalls()
{
return $this->calls;
@@ -208,7 +274,7 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab
$this->calls = [];
}
- protected function start($name)
+ protected function start(string $name)
{
$this->calls[] = $event = new TraceableAdapterEvent();
$event->name = $name;
diff --git a/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php
index de68955d8..69461b8b6 100644
--- a/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php
+++ b/lib/symfony/cache/Adapter/TraceableTagAwareAdapter.php
@@ -11,10 +11,12 @@
namespace Symfony\Component\Cache\Adapter;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
/**
* @author Robin Chalas
*/
-final class CacheItem implements CacheItemInterface
+final class CacheItem implements ItemInterface
{
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
- protected $tags = [];
- protected $prevTags = [];
+ protected $metadata = [];
+ protected $newMetadata = [];
protected $innerItem;
protected $poolHash;
+ protected $isTaggable = false;
/**
* {@inheritdoc}
*/
- public function getKey()
+ public function getKey(): string
{
return $this->key;
}
/**
* {@inheritdoc}
+ *
+ * @return mixed
*/
public function get()
{
@@ -48,7 +54,7 @@ final class CacheItem implements CacheItemInterface
/**
* {@inheritdoc}
*/
- public function isHit()
+ public function isHit(): bool
{
return $this->isHit;
}
@@ -58,7 +64,7 @@ final class CacheItem implements CacheItemInterface
*
* @return $this
*/
- public function set($value)
+ public function set($value): self
{
$this->value = $value;
@@ -70,14 +76,14 @@ final class CacheItem implements CacheItemInterface
*
* @return $this
*/
- public function expiresAt($expiration)
+ public function expiresAt($expiration): self
{
if (null === $expiration) {
$this->expiry = null;
} elseif ($expiration instanceof \DateTimeInterface) {
- $this->expiry = (int) $expiration->format('U');
+ $this->expiry = (float) $expiration->format('U.u');
} else {
- throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
+ throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', get_debug_type($expiration)));
}
return $this;
@@ -88,83 +94,77 @@ final class CacheItem implements CacheItemInterface
*
* @return $this
*/
- public function expiresAfter($time)
+ public function expiresAfter($time): self
{
if (null === $time) {
$this->expiry = null;
} elseif ($time instanceof \DateInterval) {
- $this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U');
+ $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
} elseif (\is_int($time)) {
- $this->expiry = $time + time();
+ $this->expiry = $time + microtime(true);
} else {
- throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time)));
+ throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time)));
}
return $this;
}
/**
- * Adds a tag to a cache item.
- *
- * @param string|string[] $tags A tag or array of tags
- *
- * @return $this
- *
- * @throws InvalidArgumentException When $tag is not valid
+ * {@inheritdoc}
*/
- public function tag($tags)
+ public function tag($tags): ItemInterface
{
- if (!\is_array($tags)) {
+ if (!$this->isTaggable) {
+ throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
+ }
+ if (!is_iterable($tags)) {
$tags = [$tags];
}
foreach ($tags as $tag) {
- if (!\is_string($tag)) {
- throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
+ if (!\is_string($tag) && !(\is_object($tag) && method_exists($tag, '__toString'))) {
+ throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
}
- if (isset($this->tags[$tag])) {
+ $tag = (string) $tag;
+ if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
continue;
}
if ('' === $tag) {
throw new InvalidArgumentException('Cache tag length must be greater than zero.');
}
- if (false !== strpbrk($tag, '{}()/\@:')) {
- throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:.', $tag));
+ if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS));
}
- $this->tags[$tag] = $tag;
+ $this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
}
return $this;
}
/**
- * Returns the list of tags bound to the value coming from the pool storage if any.
- *
- * @return array
+ * {@inheritdoc}
*/
- public function getPreviousTags()
+ public function getMetadata(): array
{
- return $this->prevTags;
+ return $this->metadata;
}
/**
* Validates a cache key according to PSR-6.
*
- * @param string $key The key to validate
- *
- * @return string
+ * @param mixed $key The key to validate
*
* @throws InvalidArgumentException When $key is not valid
*/
- public static function validateKey($key)
+ public static function validateKey($key): string
{
if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
}
if ('' === $key) {
throw new InvalidArgumentException('Cache key length must be greater than zero.');
}
- if (false !== strpbrk($key, '{}()/\@:')) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:.', $key));
+ if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS));
}
return $key;
@@ -175,7 +175,7 @@ final class CacheItem implements CacheItemInterface
*
* @internal
*/
- public static function log(LoggerInterface $logger = null, $message, $context = [])
+ public static function log(?LoggerInterface $logger, string $message, array $context = [])
{
if ($logger) {
$logger->warning($message, $context);
diff --git a/lib/symfony/cache/DataCollector/CacheDataCollector.php b/lib/symfony/cache/DataCollector/CacheDataCollector.php
index c9e87d5cc..9590436dc 100644
--- a/lib/symfony/cache/DataCollector/CacheDataCollector.php
+++ b/lib/symfony/cache/DataCollector/CacheDataCollector.php
@@ -21,6 +21,8 @@ use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
/**
* @author Aaron Scherer
*/
-final class CachePoolClearerPass implements CompilerPassInterface
+class CachePoolClearerPass implements CompilerPassInterface
{
+ private $cachePoolClearerTag;
+
+ public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->cachePoolClearerTag = $cachePoolClearerTag;
+ }
+
/**
* {@inheritdoc}
*/
@@ -27,12 +38,12 @@ final class CachePoolClearerPass implements CompilerPassInterface
{
$container->getParameterBag()->remove('cache.prefix.seed');
- foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) {
+ foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
$clearer = $container->getDefinition($id);
$pools = [];
- foreach ($clearer->getArgument(0) as $id => $ref) {
- if ($container->hasDefinition($id)) {
- $pools[$id] = new Reference($id);
+ foreach ($clearer->getArgument(0) as $name => $ref) {
+ if ($container->hasDefinition($ref)) {
+ $pools[$name] = new Reference($ref);
}
}
$clearer->replaceArgument(0, $pools);
diff --git a/lib/symfony/cache/DependencyInjection/CachePoolPass.php b/lib/symfony/cache/DependencyInjection/CachePoolPass.php
new file mode 100644
index 000000000..14ac2bde4
--- /dev/null
+++ b/lib/symfony/cache/DependencyInjection/CachePoolPass.php
@@ -0,0 +1,274 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DependencyInjection;
+
+use Symfony\Component\Cache\Adapter\AbstractAdapter;
+use Symfony\Component\Cache\Adapter\ArrayAdapter;
+use Symfony\Component\Cache\Adapter\ChainAdapter;
+use Symfony\Component\Cache\Adapter\NullAdapter;
+use Symfony\Component\Cache\Adapter\ParameterNormalizer;
+use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas
+ */
+class CachePoolPass implements CompilerPassInterface
+{
+ private $cachePoolTag;
+ private $kernelResetTag;
+ private $cacheClearerId;
+ private $cachePoolClearerTag;
+ private $cacheSystemClearerId;
+ private $cacheSystemClearerTag;
+ private $reverseContainerId;
+ private $reversibleTag;
+ private $messageHandlerId;
+
+ public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer', string $reverseContainerId = 'reverse_container', string $reversibleTag = 'container.reversible', string $messageHandlerId = 'cache.early_expiration_handler')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->cachePoolTag = $cachePoolTag;
+ $this->kernelResetTag = $kernelResetTag;
+ $this->cacheClearerId = $cacheClearerId;
+ $this->cachePoolClearerTag = $cachePoolClearerTag;
+ $this->cacheSystemClearerId = $cacheSystemClearerId;
+ $this->cacheSystemClearerTag = $cacheSystemClearerTag;
+ $this->reverseContainerId = $reverseContainerId;
+ $this->reversibleTag = $reversibleTag;
+ $this->messageHandlerId = $messageHandlerId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if ($container->hasParameter('cache.prefix.seed')) {
+ $seed = $container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
+ } else {
+ $seed = '_'.$container->getParameter('kernel.project_dir');
+ $seed .= '.'.$container->getParameter('kernel.container_class');
+ }
+
+ $needsMessageHandler = false;
+ $allPools = [];
+ $clearers = [];
+ $attributes = [
+ 'provider',
+ 'name',
+ 'namespace',
+ 'default_lifetime',
+ 'early_expiration_message_bus',
+ 'reset',
+ ];
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
+ $adapter = $pool = $container->getDefinition($id);
+ if ($pool->isAbstract()) {
+ continue;
+ }
+ $class = $adapter->getClass();
+ while ($adapter instanceof ChildDefinition) {
+ $adapter = $container->findDefinition($adapter->getParent());
+ $class = $class ?: $adapter->getClass();
+ if ($t = $adapter->getTag($this->cachePoolTag)) {
+ $tags[0] += $t[0];
+ }
+ }
+ $name = $tags[0]['name'] ?? $id;
+ if (!isset($tags[0]['namespace'])) {
+ $namespaceSeed = $seed;
+ if (null !== $class) {
+ $namespaceSeed .= '.'.$class;
+ }
+
+ $tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name);
+ }
+ if (isset($tags[0]['clearer'])) {
+ $clearer = $tags[0]['clearer'];
+ while ($container->hasAlias($clearer)) {
+ $clearer = (string) $container->getAlias($clearer);
+ }
+ } else {
+ $clearer = null;
+ }
+ unset($tags[0]['clearer'], $tags[0]['name']);
+
+ if (isset($tags[0]['provider'])) {
+ $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
+ }
+
+ if (ChainAdapter::class === $class) {
+ $adapters = [];
+ foreach ($adapter->getArgument(0) as $provider => $adapter) {
+ if ($adapter instanceof ChildDefinition) {
+ $chainedPool = $adapter;
+ } else {
+ $chainedPool = $adapter = new ChildDefinition($adapter);
+ }
+
+ $chainedTags = [\is_int($provider) ? [] : ['provider' => $provider]];
+ $chainedClass = '';
+
+ while ($adapter instanceof ChildDefinition) {
+ $adapter = $container->findDefinition($adapter->getParent());
+ $chainedClass = $chainedClass ?: $adapter->getClass();
+ if ($t = $adapter->getTag($this->cachePoolTag)) {
+ $chainedTags[0] += $t[0];
+ }
+ }
+
+ if (ChainAdapter::class === $chainedClass) {
+ throw new InvalidArgumentException(sprintf('Invalid service "%s": chain of adapters cannot reference another chain, found "%s".', $id, $chainedPool->getParent()));
+ }
+
+ $i = 0;
+
+ if (isset($chainedTags[0]['provider'])) {
+ $chainedPool->replaceArgument($i++, new Reference(static::getServiceProvider($container, $chainedTags[0]['provider'])));
+ }
+
+ if (isset($tags[0]['namespace']) && !\in_array($adapter->getClass(), [ArrayAdapter::class, NullAdapter::class], true)) {
+ $chainedPool->replaceArgument($i++, $tags[0]['namespace']);
+ }
+
+ if (isset($tags[0]['default_lifetime'])) {
+ $chainedPool->replaceArgument($i++, $tags[0]['default_lifetime']);
+ }
+
+ $adapters[] = $chainedPool;
+ }
+
+ $pool->replaceArgument(0, $adapters);
+ unset($tags[0]['provider'], $tags[0]['namespace']);
+ $i = 1;
+ } else {
+ $i = 0;
+ }
+
+ foreach ($attributes as $attr) {
+ if (!isset($tags[0][$attr])) {
+ // no-op
+ } elseif ('reset' === $attr) {
+ if ($tags[0][$attr]) {
+ $pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
+ }
+ } elseif ('early_expiration_message_bus' === $attr) {
+ $needsMessageHandler = true;
+ $pool->addMethodCall('setCallbackWrapper', [(new Definition(EarlyExpirationDispatcher::class))
+ ->addArgument(new Reference($tags[0]['early_expiration_message_bus']))
+ ->addArgument(new Reference($this->reverseContainerId))
+ ->addArgument((new Definition('callable'))
+ ->setFactory([new Reference($id), 'setCallbackWrapper'])
+ ->addArgument(null)
+ ),
+ ]);
+ $pool->addTag($this->reversibleTag);
+ } elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class], true)) {
+ $argument = $tags[0][$attr];
+
+ if ('default_lifetime' === $attr && !is_numeric($argument)) {
+ $argument = (new Definition('int', [$argument]))
+ ->setFactory([ParameterNormalizer::class, 'normalizeDuration']);
+ }
+
+ $pool->replaceArgument($i++, $argument);
+ }
+ unset($tags[0][$attr]);
+ }
+ if (!empty($tags[0])) {
+ throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
+ }
+
+ if (null !== $clearer) {
+ $clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
+ }
+
+ $allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
+ }
+
+ if (!$needsMessageHandler) {
+ $container->removeDefinition($this->messageHandlerId);
+ }
+
+ $notAliasedCacheClearerId = $this->cacheClearerId;
+ while ($container->hasAlias($this->cacheClearerId)) {
+ $this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
+ }
+ if ($container->hasDefinition($this->cacheClearerId)) {
+ $clearers[$notAliasedCacheClearerId] = $allPools;
+ }
+
+ foreach ($clearers as $id => $pools) {
+ $clearer = $container->getDefinition($id);
+ if ($clearer instanceof ChildDefinition) {
+ $clearer->replaceArgument(0, $pools);
+ } else {
+ $clearer->setArgument(0, $pools);
+ }
+ $clearer->addTag($this->cachePoolClearerTag);
+
+ if ($this->cacheSystemClearerId === $id) {
+ $clearer->addTag($this->cacheSystemClearerTag);
+ }
+ }
+
+ $allPoolsKeys = array_keys($allPools);
+
+ if ($container->hasDefinition('console.command.cache_pool_list')) {
+ $container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, $allPoolsKeys);
+ }
+
+ if ($container->hasDefinition('console.command.cache_pool_clear')) {
+ $container->getDefinition('console.command.cache_pool_clear')->addArgument($allPoolsKeys);
+ }
+
+ if ($container->hasDefinition('console.command.cache_pool_delete')) {
+ $container->getDefinition('console.command.cache_pool_delete')->addArgument($allPoolsKeys);
+ }
+ }
+
+ private function getNamespace(string $seed, string $id)
+ {
+ return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
+ }
+
+ /**
+ * @internal
+ */
+ public static function getServiceProvider(ContainerBuilder $container, string $name)
+ {
+ $container->resolveEnvPlaceholders($name, null, $usedEnvs);
+
+ if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
+ $dsn = $name;
+
+ if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
+ $definition = new Definition(AbstractAdapter::class);
+ $definition->setPublic(false);
+ $definition->setFactory([AbstractAdapter::class, 'createConnection']);
+ $definition->setArguments([$dsn, ['lazy' => true]]);
+ $container->setDefinition($name, $definition);
+ }
+ }
+
+ return $name;
+ }
+}
diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPrunerPass.php b/lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
similarity index 84%
rename from lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPrunerPass.php
rename to lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
index 597f7dc06..86a1906ad 100644
--- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPrunerPass.php
+++ b/lib/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
+namespace Symfony\Component\Cache\DependencyInjection;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
@@ -26,8 +26,12 @@ class CachePoolPrunerPass implements CompilerPassInterface
private $cacheCommandServiceId;
private $cachePoolTag;
- public function __construct($cacheCommandServiceId = 'console.command.cache_pool_prune', $cachePoolTag = 'cache.pool')
+ public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
{
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
$this->cacheCommandServiceId = $cacheCommandServiceId;
$this->cachePoolTag = $cachePoolTag;
}
diff --git a/lib/symfony/cache/DoctrineProvider.php b/lib/symfony/cache/DoctrineProvider.php
index 4c5cd0cb1..7b55aae23 100644
--- a/lib/symfony/cache/DoctrineProvider.php
+++ b/lib/symfony/cache/DoctrineProvider.php
@@ -13,9 +13,16 @@ namespace Symfony\Component\Cache;
use Doctrine\Common\Cache\CacheProvider;
use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+if (!class_exists(CacheProvider::class)) {
+ return;
+}
/**
* @author Nicolas Grekas
+ *
+ * @deprecated Use Doctrine\Common\Cache\Psr6\DoctrineProvider instead
*/
class DoctrineProvider extends CacheProvider implements PruneableInterface, ResettableInterface
{
@@ -23,6 +30,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
public function __construct(CacheItemPoolInterface $pool)
{
+ trigger_deprecation('symfony/cache', '5.4', '"%s" is deprecated, use "Doctrine\Common\Cache\Psr6\DoctrineProvider" instead.', __CLASS__);
+
$this->pool = $pool;
}
@@ -39,7 +48,7 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
*/
public function reset()
{
- if ($this->pool instanceof ResettableInterface) {
+ if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
@@ -47,6 +56,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
+ *
+ * @return mixed
*/
protected function doFetch($id)
{
@@ -57,6 +68,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doContains($id)
{
@@ -65,6 +78,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doSave($id, $data, $lifeTime = 0)
{
@@ -79,6 +94,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doDelete($id)
{
@@ -87,6 +104,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
protected function doFlush()
{
@@ -95,6 +114,8 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
/**
* {@inheritdoc}
+ *
+ * @return array|null
*/
protected function doGetStats()
{
diff --git a/lib/symfony/cache/Exception/CacheException.php b/lib/symfony/cache/Exception/CacheException.php
index e87b2db8f..d2e975b2b 100644
--- a/lib/symfony/cache/Exception/CacheException.php
+++ b/lib/symfony/cache/Exception/CacheException.php
@@ -14,6 +14,12 @@ namespace Symfony\Component\Cache\Exception;
use Psr\Cache\CacheException as Psr6CacheInterface;
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
-class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
-{
+if (interface_exists(SimpleCacheInterface::class)) {
+ class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class CacheException extends \Exception implements Psr6CacheInterface
+ {
+ }
}
diff --git a/lib/symfony/cache/Exception/InvalidArgumentException.php b/lib/symfony/cache/Exception/InvalidArgumentException.php
index 828bf3ed7..7f9584a26 100644
--- a/lib/symfony/cache/Exception/InvalidArgumentException.php
+++ b/lib/symfony/cache/Exception/InvalidArgumentException.php
@@ -14,6 +14,12 @@ namespace Symfony\Component\Cache\Exception;
use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
-class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
-{
+if (interface_exists(SimpleCacheInterface::class)) {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
+ {
+ }
}
diff --git a/lib/symfony/cache/Exception/LogicException.php b/lib/symfony/cache/Exception/LogicException.php
new file mode 100644
index 000000000..9ffa7ed69
--- /dev/null
+++ b/lib/symfony/cache/Exception/LogicException.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Exception;
+
+use Psr\Cache\CacheException as Psr6CacheInterface;
+use Psr\SimpleCache\CacheException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+ class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class LogicException extends \LogicException implements Psr6CacheInterface
+ {
+ }
+}
diff --git a/lib/symfony/cache/LICENSE b/lib/symfony/cache/LICENSE
index a7ec70801..7fa953905 100644
--- a/lib/symfony/cache/LICENSE
+++ b/lib/symfony/cache/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2016-2020 Fabien Potencier
+Copyright (c) 2016-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/lib/symfony/cache/LockRegistry.php b/lib/symfony/cache/LockRegistry.php
new file mode 100644
index 000000000..65f20bb73
--- /dev/null
+++ b/lib/symfony/cache/LockRegistry.php
@@ -0,0 +1,165 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * LockRegistry is used internally by existing adapters to protect against cache stampede.
+ *
+ * It does so by wrapping the computation of items in a pool of locks.
+ * Foreach each apps, there can be at most 20 concurrent processes that
+ * compute items at the same time and only one per cache-key.
+ *
+ * @author Nicolas Grekas
+ */
+final class LockRegistry
+{
+ private static $openedFiles = [];
+ private static $lockedFiles;
+ private static $signalingException;
+ private static $signalingCallback;
+
+ /**
+ * The number of items in this list controls the max number of concurrent processes.
+ */
+ private static $files = [
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ParameterNormalizer.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
+ __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
+ ];
+
+ /**
+ * Defines a set of existing files that will be used as keys to acquire locks.
+ *
+ * @return array The previously defined set of files
+ */
+ public static function setFiles(array $files): array
+ {
+ $previousFiles = self::$files;
+ self::$files = $files;
+
+ foreach (self::$openedFiles as $file) {
+ if ($file) {
+ flock($file, \LOCK_UN);
+ fclose($file);
+ }
+ }
+ self::$openedFiles = self::$lockedFiles = [];
+
+ return $previousFiles;
+ }
+
+ public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) {
+ // disable locking on Windows by default
+ self::$files = self::$lockedFiles = [];
+ }
+
+ $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
+
+ if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) {
+ return $callback($item, $save);
+ }
+
+ self::$signalingException ?? self::$signalingException = unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
+ self::$signalingCallback ?? self::$signalingCallback = function () { throw self::$signalingException; };
+
+ while (true) {
+ try {
+ $locked = false;
+ // race to get the lock in non-blocking mode
+ $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
+
+ if ($locked || !$wouldBlock) {
+ $logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
+ self::$lockedFiles[$key] = true;
+
+ $value = $callback($item, $save);
+
+ if ($save) {
+ if ($setMetadata) {
+ $setMetadata($item);
+ }
+
+ $pool->save($item->set($value));
+ $save = false;
+ }
+
+ return $value;
+ }
+ // if we failed the race, retry locking in blocking mode to wait for the winner
+ $logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
+ flock($lock, \LOCK_SH);
+ } finally {
+ flock($lock, \LOCK_UN);
+ unset(self::$lockedFiles[$key]);
+ }
+
+ try {
+ $value = $pool->get($item->getKey(), self::$signalingCallback, 0);
+ $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
+ $save = false;
+
+ return $value;
+ } catch (\Exception $e) {
+ if (self::$signalingException !== $e) {
+ throw $e;
+ }
+ $logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
+ }
+ }
+
+ return null;
+ }
+
+ private static function open(int $key)
+ {
+ if (null !== $h = self::$openedFiles[$key] ?? null) {
+ return $h;
+ }
+ set_error_handler(function () {});
+ try {
+ $h = fopen(self::$files[$key], 'r+');
+ } finally {
+ restore_error_handler();
+ }
+
+ return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
+ }
+}
diff --git a/lib/symfony/cache/Marshaller/DefaultMarshaller.php b/lib/symfony/cache/Marshaller/DefaultMarshaller.php
new file mode 100644
index 000000000..3202dd69c
--- /dev/null
+++ b/lib/symfony/cache/Marshaller/DefaultMarshaller.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
+ *
+ * @author Nicolas Grekas
+ */
+class DefaultMarshaller implements MarshallerInterface
+{
+ private $useIgbinarySerialize = true;
+ private $throwOnSerializationFailure;
+
+ public function __construct(bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false)
+ {
+ if (null === $useIgbinarySerialize) {
+ $useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.6', phpversion('igbinary'), '<='));
+ } elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.6', phpversion('igbinary'), '>')))) {
+ throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.');
+ }
+ $this->useIgbinarySerialize = $useIgbinarySerialize;
+ $this->throwOnSerializationFailure = $throwOnSerializationFailure;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ $serialized = $failed = [];
+
+ foreach ($values as $id => $value) {
+ try {
+ if ($this->useIgbinarySerialize) {
+ $serialized[$id] = igbinary_serialize($value);
+ } else {
+ $serialized[$id] = serialize($value);
+ }
+ } catch (\Exception $e) {
+ if ($this->throwOnSerializationFailure) {
+ throw new \ValueError($e->getMessage(), 0, $e);
+ }
+ $failed[] = $id;
+ }
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ if ('b:0;' === $value) {
+ return false;
+ }
+ if ('N;' === $value) {
+ return null;
+ }
+ static $igbinaryNull;
+ if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) {
+ return null;
+ }
+ $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
+ try {
+ if (':' === ($value[1] ?? ':')) {
+ if (false !== $value = unserialize($value)) {
+ return $value;
+ }
+ } elseif (false === $igbinaryNull) {
+ throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
+ } elseif (null !== $value = igbinary_unserialize($value)) {
+ return $value;
+ }
+
+ throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
+ } catch (\Error $e) {
+ throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
+ } finally {
+ ini_set('unserialize_callback_func', $unserializeCallbackHandler);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleUnserializeCallback(string $class)
+ {
+ throw new \DomainException('Class not found: '.$class);
+ }
+}
diff --git a/lib/symfony/cache/Marshaller/DeflateMarshaller.php b/lib/symfony/cache/Marshaller/DeflateMarshaller.php
new file mode 100644
index 000000000..554480611
--- /dev/null
+++ b/lib/symfony/cache/Marshaller/DeflateMarshaller.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+use Symfony\Component\Cache\Exception\CacheException;
+
+/**
+ * Compresses values using gzdeflate().
+ *
+ * @author Nicolas Grekas
+ */
+class DeflateMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+
+ public function __construct(MarshallerInterface $marshaller)
+ {
+ if (!\function_exists('gzdeflate')) {
+ throw new CacheException('The "zlib" PHP extension is not loaded.');
+ }
+
+ $this->marshaller = $marshaller;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ return array_map('gzdeflate', $this->marshaller->marshall($values, $failed));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ if (false !== $inflatedValue = @gzinflate($value)) {
+ $value = $inflatedValue;
+ }
+
+ return $this->marshaller->unmarshall($value);
+ }
+}
diff --git a/lib/symfony/cache/Marshaller/MarshallerInterface.php b/lib/symfony/cache/Marshaller/MarshallerInterface.php
new file mode 100644
index 000000000..cdd6c4022
--- /dev/null
+++ b/lib/symfony/cache/Marshaller/MarshallerInterface.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+/**
+ * Serializes/unserializes PHP values.
+ *
+ * Implementations of this interface MUST deal with errors carefully. They MUST
+ * also deal with forward and backward compatibility at the storage format level.
+ *
+ * @author Nicolas Grekas
+ */
+interface MarshallerInterface
+{
+ /**
+ * Serializes a list of values.
+ *
+ * When serialization fails for a specific value, no exception should be
+ * thrown. Instead, its key should be listed in $failed.
+ */
+ public function marshall(array $values, ?array &$failed): array;
+
+ /**
+ * Unserializes a single value and throws an exception if anything goes wrong.
+ *
+ * @return mixed
+ *
+ * @throws \Exception Whenever unserialization fails
+ */
+ public function unmarshall(string $value);
+}
diff --git a/lib/symfony/cache/Marshaller/SodiumMarshaller.php b/lib/symfony/cache/Marshaller/SodiumMarshaller.php
new file mode 100644
index 000000000..dbf486a72
--- /dev/null
+++ b/lib/symfony/cache/Marshaller/SodiumMarshaller.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+
+/**
+ * Encrypt/decrypt values using Libsodium.
+ *
+ * @author Ahmed TAILOULOUTE
+ */
+class TagAwareMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+
+ public function __construct(MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ $failed = $notSerialized = $serialized = [];
+
+ foreach ($values as $id => $value) {
+ if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) {
+ // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format
+ // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall()
+
+ $v = $this->marshaller->marshall($value, $f);
+
+ if ($f) {
+ $f = [];
+ $failed[] = $id;
+ } else {
+ if ([] === $value['tags']) {
+ $v['tags'] = '';
+ }
+
+ $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value'];
+ $serialized[$id][9] = "\x5F";
+ }
+ } else {
+ // other arbitratry values are serialized using the decorated marshaller below
+ $notSerialized[$id] = $value;
+ }
+ }
+
+ if ($notSerialized) {
+ $serialized += $this->marshaller->marshall($notSerialized, $f);
+ $failed = array_merge($failed, $f);
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) {
+ return $this->marshaller->unmarshall($value);
+ }
+
+ // data consists of value, tags and metadata which we need to unpack
+ $meta = substr($value, 1, 12);
+ $meta[8] = "\0";
+ $tagLen = unpack('Nlen', $meta, 8)['len'];
+ $meta = substr($meta, 0, 8);
+
+ return [
+ 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)),
+ 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [],
+ 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta,
+ ];
+ }
+}
diff --git a/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php b/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php
new file mode 100644
index 000000000..6f11b8b5a
--- /dev/null
+++ b/lib/symfony/cache/Messenger/EarlyExpirationDispatcher.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Messenger;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\DependencyInjection\ReverseContainer;
+use Symfony\Component\Messenger\MessageBusInterface;
+use Symfony\Component\Messenger\Stamp\HandledStamp;
+
+/**
+ * Sends the computation of cached values to a message bus.
+ */
+class EarlyExpirationDispatcher
+{
+ private $bus;
+ private $reverseContainer;
+ private $callbackWrapper;
+
+ public function __construct(MessageBusInterface $bus, ReverseContainer $reverseContainer, callable $callbackWrapper = null)
+ {
+ $this->bus = $bus;
+ $this->reverseContainer = $reverseContainer;
+ $this->callbackWrapper = $callbackWrapper;
+ }
+
+ public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, LoggerInterface $logger = null)
+ {
+ if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) {
+ // The item is stale or the callback cannot be reversed: we must compute the value now
+ $logger && $logger->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]);
+
+ return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save);
+ }
+
+ $envelope = $this->bus->dispatch($message);
+
+ if ($logger) {
+ if ($envelope->last(HandledStamp::class)) {
+ $logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]);
+ } else {
+ $logger->info('Item "{key}" sent for recomputation', ['key' => $item->getKey()]);
+ }
+ }
+
+ // The item's value is not stale, no need to write it to the backend
+ $save = false;
+
+ return $message->getItem()->get() ?? $item->get();
+ }
+}
diff --git a/lib/symfony/cache/Messenger/EarlyExpirationHandler.php b/lib/symfony/cache/Messenger/EarlyExpirationHandler.php
new file mode 100644
index 000000000..1f0bd565c
--- /dev/null
+++ b/lib/symfony/cache/Messenger/EarlyExpirationHandler.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Messenger;
+
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\DependencyInjection\ReverseContainer;
+use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
+
+/**
+ * Computes cached values sent to a message bus.
+ */
+class EarlyExpirationHandler implements MessageHandlerInterface
+{
+ private $reverseContainer;
+ private $processedNonces = [];
+
+ public function __construct(ReverseContainer $reverseContainer)
+ {
+ $this->reverseContainer = $reverseContainer;
+ }
+
+ public function __invoke(EarlyExpirationMessage $message)
+ {
+ $item = $message->getItem();
+ $metadata = $item->getMetadata();
+ $expiry = $metadata[CacheItem::METADATA_EXPIRY] ?? 0;
+ $ctime = $metadata[CacheItem::METADATA_CTIME] ?? 0;
+
+ if ($expiry && $ctime) {
+ // skip duplicate or expired messages
+
+ $processingNonce = [$expiry, $ctime];
+ $pool = $message->getPool();
+ $key = $item->getKey();
+
+ if (($this->processedNonces[$pool][$key] ?? null) === $processingNonce) {
+ return;
+ }
+
+ if (microtime(true) >= $expiry) {
+ return;
+ }
+
+ $this->processedNonces[$pool] = [$key => $processingNonce] + ($this->processedNonces[$pool] ?? []);
+
+ if (\count($this->processedNonces[$pool]) > 100) {
+ array_pop($this->processedNonces[$pool]);
+ }
+ }
+
+ static $setMetadata;
+
+ $setMetadata ?? $setMetadata = \Closure::bind(
+ function (CacheItem $item, float $startTime) {
+ if ($item->expiry > $endTime = microtime(true)) {
+ $item->newMetadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
+ $item->newMetadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime));
+ }
+ },
+ null,
+ CacheItem::class
+ );
+
+ $startTime = microtime(true);
+ $pool = $message->findPool($this->reverseContainer);
+ $callback = $message->findCallback($this->reverseContainer);
+ $value = $callback($item);
+ $setMetadata($item, $startTime);
+ $pool->save($item->set($value));
+ }
+}
diff --git a/lib/symfony/cache/Messenger/EarlyExpirationMessage.php b/lib/symfony/cache/Messenger/EarlyExpirationMessage.php
new file mode 100644
index 000000000..e25c07e9a
--- /dev/null
+++ b/lib/symfony/cache/Messenger/EarlyExpirationMessage.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Messenger;
+
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\DependencyInjection\ReverseContainer;
+
+/**
+ * Conveys a cached value that needs to be computed.
+ */
+final class EarlyExpirationMessage
+{
+ private $item;
+ private $pool;
+ private $callback;
+
+ public static function create(ReverseContainer $reverseContainer, callable $callback, CacheItem $item, AdapterInterface $pool): ?self
+ {
+ try {
+ $item = clone $item;
+ $item->set(null);
+ } catch (\Exception $e) {
+ return null;
+ }
+
+ $pool = $reverseContainer->getId($pool);
+
+ if (\is_object($callback)) {
+ if (null === $id = $reverseContainer->getId($callback)) {
+ return null;
+ }
+
+ $callback = '@'.$id;
+ } elseif (!\is_array($callback)) {
+ $callback = (string) $callback;
+ } elseif (!\is_object($callback[0])) {
+ $callback = [(string) $callback[0], (string) $callback[1]];
+ } else {
+ if (null === $id = $reverseContainer->getId($callback[0])) {
+ return null;
+ }
+
+ $callback = ['@'.$id, (string) $callback[1]];
+ }
+
+ return new self($item, $pool, $callback);
+ }
+
+ public function getItem(): CacheItem
+ {
+ return $this->item;
+ }
+
+ public function getPool(): string
+ {
+ return $this->pool;
+ }
+
+ public function getCallback()
+ {
+ return $this->callback;
+ }
+
+ public function findPool(ReverseContainer $reverseContainer): AdapterInterface
+ {
+ return $reverseContainer->getService($this->pool);
+ }
+
+ public function findCallback(ReverseContainer $reverseContainer): callable
+ {
+ if (\is_string($callback = $this->callback)) {
+ return '@' === $callback[0] ? $reverseContainer->getService(substr($callback, 1)) : $callback;
+ }
+ if ('@' === $callback[0][0]) {
+ $callback[0] = $reverseContainer->getService(substr($callback[0], 1));
+ }
+
+ return $callback;
+ }
+
+ private function __construct(CacheItem $item, string $pool, $callback)
+ {
+ $this->item = $item;
+ $this->pool = $pool;
+ $this->callback = $callback;
+ }
+}
diff --git a/lib/symfony/cache/Simple/Psr6Cache.php b/lib/symfony/cache/Psr16Cache.php
similarity index 75%
rename from lib/symfony/cache/Simple/Psr6Cache.php
rename to lib/symfony/cache/Psr16Cache.php
index 6b3de2059..28c7de605 100644
--- a/lib/symfony/cache/Simple/Psr6Cache.php
+++ b/lib/symfony/cache/Psr16Cache.php
@@ -9,26 +9,31 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Cache\Simple;
+namespace Symfony\Component\Cache;
use Psr\Cache\CacheException as Psr6CacheException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheException as SimpleCacheException;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
-use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
+if (null !== (new \ReflectionMethod(CacheInterface::class, 'get'))->getReturnType()) {
+ throw new \LogicException('psr/simple-cache 3.0+ is not compatible with this version of symfony/cache. Please upgrade symfony/cache to 6.0+ or downgrade psr/simple-cache to 1.x or 2.x.');
+}
+
/**
+ * Turns a PSR-6 cache into a PSR-16 one.
+ *
* @author Nicolas Grekas
*/
-class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterface
+class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
private $createCacheItem;
private $cacheItemPrototype;
@@ -43,7 +48,13 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
$createCacheItem = \Closure::bind(
static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
$item = clone $cacheItemPrototype;
- $item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
+ $item->poolHash = $item->innerItem = null;
+ if ($allowInt && \is_int($key)) {
+ $item->key = (string) $key;
+ } else {
+ \assert('' !== CacheItem::validateKey($key));
+ $item->key = $key;
+ }
$item->value = $value;
$item->isHit = false;
@@ -58,12 +69,14 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
}
$this->createCacheItem = $createCacheItem;
- return $createCacheItem($key, $value, $allowInt);
+ return $createCacheItem($key, null, $allowInt)->set($value);
};
}
/**
* {@inheritdoc}
+ *
+ * @return mixed
*/
public function get($key, $default = null)
{
@@ -84,6 +97,8 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function set($key, $value, $ttl = null)
{
@@ -107,6 +122,8 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function delete($key)
{
@@ -121,6 +138,8 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function clear()
{
@@ -129,13 +148,15 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return iterable
*/
public function getMultiple($keys, $default = null)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys)));
}
try {
@@ -147,8 +168,29 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
}
$values = [];
+ if (!$this->pool instanceof AdapterInterface) {
+ foreach ($items as $key => $item) {
+ $values[$key] = $item->isHit() ? $item->get() : $default;
+ }
+
+ return $values;
+ }
+
foreach ($items as $key => $item) {
- $values[$key] = $item->isHit() ? $item->get() : $default;
+ if (!$item->isHit()) {
+ $values[$key] = $default;
+ continue;
+ }
+ $values[$key] = $item->get();
+
+ if (!$metadata = $item->getMetadata()) {
+ continue;
+ }
+ unset($metadata[CacheItem::METADATA_TAGS]);
+
+ if ($metadata) {
+ $values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]];
+ }
}
return $values;
@@ -156,12 +198,14 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function setMultiple($values, $ttl = null)
{
$valuesIsArray = \is_array($values);
if (!$valuesIsArray && !$values instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
+ throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', get_debug_type($values)));
}
$items = [];
@@ -207,13 +251,15 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function deleteMultiple($keys)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
+ throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys)));
}
try {
@@ -227,6 +273,8 @@ class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterfa
/**
* {@inheritdoc}
+ *
+ * @return bool
*/
public function has($key)
{
diff --git a/lib/symfony/cache/README.md b/lib/symfony/cache/README.md
index c4ab7520f..74052052c 100644
--- a/lib/symfony/cache/README.md
+++ b/lib/symfony/cache/README.md
@@ -1,18 +1,19 @@
Symfony PSR-6 implementation for caching
========================================
-This component provides an extended [PSR-6](http://www.php-fig.org/psr/psr-6/)
-implementation for adding cache to your applications. It is designed to have a
-low overhead so that caching is fastest. It ships with a few caching adapters
-for the most widespread and suited to caching backends. It also provides a
-`doctrine/cache` proxy adapter to cover more advanced caching needs and a proxy
-adapter for greater interoperability between PSR-6 implementations.
+The Cache component provides an extended
+[PSR-6](http://www.php-fig.org/psr/psr-6/) implementation for adding cache to
+your applications. It is designed to have a low overhead so that caching is
+fastest. It ships with a few caching adapters for the most widespread and
+suited to caching backends. It also provides a `doctrine/cache` proxy adapter
+to cover more advanced caching needs and a proxy adapter for greater
+interoperability between PSR-6 implementations.
Resources
---------
- * [Documentation](https://symfony.com/doc/current/components/cache.html)
- * [Contributing](https://symfony.com/doc/current/contributing/index.html)
- * [Report issues](https://github.com/symfony/symfony/issues) and
- [send Pull Requests](https://github.com/symfony/symfony/pulls)
- in the [main Symfony repository](https://github.com/symfony/symfony)
+ * [Documentation](https://symfony.com/doc/current/components/cache.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/lib/symfony/cache/ResettableInterface.php b/lib/symfony/cache/ResettableInterface.php
index 6be72861e..7b0a853f2 100644
--- a/lib/symfony/cache/ResettableInterface.php
+++ b/lib/symfony/cache/ResettableInterface.php
@@ -11,10 +11,11 @@
namespace Symfony\Component\Cache;
+use Symfony\Contracts\Service\ResetInterface;
+
/**
* Resets a pool's local state.
*/
-interface ResettableInterface
+interface ResettableInterface extends ResetInterface
{
- public function reset();
}
diff --git a/lib/symfony/cache/Simple/AbstractCache.php b/lib/symfony/cache/Simple/AbstractCache.php
deleted file mode 100644
index baedb7374..000000000
--- a/lib/symfony/cache/Simple/AbstractCache.php
+++ /dev/null
@@ -1,190 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\SimpleCache\CacheInterface;
-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
- */
-abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
-{
- /**
- * @internal
- */
- const NS_SEPARATOR = ':';
-
- use AbstractTrait {
- deleteItems as private;
- AbstractTrait::deleteItem as delete;
- AbstractTrait::hasItem as has;
- }
-
- private $defaultLifetime;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- protected function __construct($namespace = '', $defaultLifetime = 0)
- {
- $this->defaultLifetime = max(0, (int) $defaultLifetime);
- $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
- 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));
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- $id = $this->getId($key);
-
- try {
- foreach ($this->doFetch([$id]) as $value) {
- return $value;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
- }
-
- return $default;
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- CacheItem::validateKey($key);
-
- return $this->setMultiple([$key => $value], $ttl);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- if ($keys instanceof \Traversable) {
- $keys = iterator_to_array($keys, false);
- } elseif (!\is_array($keys)) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
- $ids = [];
-
- foreach ($keys as $key) {
- $ids[] = $this->getId($key);
- }
- try {
- $values = $this->doFetch($ids);
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => $keys, 'exception' => $e]);
- $values = [];
- }
- $ids = array_combine($ids, $keys);
-
- return $this->generateValues($values, $ids, $default);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- if (!\is_array($values) && !$values instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
- }
- $valuesById = [];
-
- foreach ($values as $key => $value) {
- if (\is_int($key)) {
- $key = (string) $key;
- }
- $valuesById[$this->getId($key)] = $value;
- }
- if (false === $ttl = $this->normalizeTtl($ttl)) {
- return $this->doDelete(array_keys($valuesById));
- }
-
- try {
- $e = $this->doSave($valuesById, $ttl);
- } catch (\Exception $e) {
- }
- if (true === $e || [] === $e) {
- return true;
- }
- $keys = [];
- foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
- $keys[] = substr($id, \strlen($this->namespace));
- }
- CacheItem::log($this->logger, 'Failed to save values', ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
-
- return false;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- if ($keys instanceof \Traversable) {
- $keys = iterator_to_array($keys, false);
- } elseif (!\is_array($keys)) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
-
- return $this->deleteItems($keys);
- }
-
- private function normalizeTtl($ttl)
- {
- if (null === $ttl) {
- return $this->defaultLifetime;
- }
- if ($ttl instanceof \DateInterval) {
- $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
- }
- if (\is_int($ttl)) {
- return 0 < $ttl ? $ttl : false;
- }
-
- throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
- }
-
- private function generateValues($values, &$keys, $default)
- {
- try {
- foreach ($values as $id => $value) {
- if (!isset($keys[$id])) {
- $id = key($keys);
- }
- $key = $keys[$id];
- unset($keys[$id]);
- yield $key => $value;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => array_values($keys), 'exception' => $e]);
- }
-
- foreach ($keys as $key) {
- yield $key => $default;
- }
- }
-}
diff --git a/lib/symfony/cache/Simple/ApcuCache.php b/lib/symfony/cache/Simple/ApcuCache.php
deleted file mode 100644
index e583b4434..000000000
--- a/lib/symfony/cache/Simple/ApcuCache.php
+++ /dev/null
@@ -1,29 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Symfony\Component\Cache\Traits\ApcuTrait;
-
-class ApcuCache extends AbstractCache
-{
- use ApcuTrait;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $version
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
- {
- $this->init($namespace, $defaultLifetime, $version);
- }
-}
diff --git a/lib/symfony/cache/Simple/ArrayCache.php b/lib/symfony/cache/Simple/ArrayCache.php
deleted file mode 100644
index 6013f0ad2..000000000
--- a/lib/symfony/cache/Simple/ArrayCache.php
+++ /dev/null
@@ -1,148 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Psr\Log\LoggerAwareInterface;
-use Psr\SimpleCache\CacheInterface;
-use Symfony\Component\Cache\CacheItem;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-use Symfony\Component\Cache\ResettableInterface;
-use Symfony\Component\Cache\Traits\ArrayTrait;
-
-/**
- * @author Nicolas Grekas
- */
-class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
-{
- use ArrayTrait {
- ArrayTrait::deleteItem as delete;
- ArrayTrait::hasItem as has;
- }
-
- private $defaultLifetime;
-
- /**
- * @param int $defaultLifetime
- * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
- */
- public function __construct($defaultLifetime = 0, $storeSerialized = true)
- {
- $this->defaultLifetime = (int) $defaultLifetime;
- $this->storeSerialized = $storeSerialized;
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- foreach ($this->getMultiple([$key], $default) as $v) {
- return $v;
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- if ($keys instanceof \Traversable) {
- $keys = iterator_to_array($keys, false);
- } elseif (!\is_array($keys)) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
- foreach ($keys as $key) {
- CacheItem::validateKey($key);
- }
-
- return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- if (!\is_array($keys) && !$keys instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
- foreach ($keys as $key) {
- $this->delete($key);
- }
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- CacheItem::validateKey($key);
-
- return $this->setMultiple([$key => $value], $ttl);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- if (!\is_array($values) && !$values instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
- }
- $valuesArray = [];
-
- foreach ($values as $key => $value) {
- \is_int($key) || CacheItem::validateKey($key);
- $valuesArray[$key] = $value;
- }
- if (false === $ttl = $this->normalizeTtl($ttl)) {
- return $this->deleteMultiple(array_keys($valuesArray));
- }
- if ($this->storeSerialized) {
- foreach ($valuesArray as $key => $value) {
- try {
- $valuesArray[$key] = serialize($value);
- } catch (\Exception $e) {
- $type = \is_object($value) ? \get_class($value) : \gettype($value);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
-
- return false;
- }
- }
- }
- $expiry = 0 < $ttl ? time() + $ttl : \PHP_INT_MAX;
-
- foreach ($valuesArray as $key => $value) {
- $this->values[$key] = $value;
- $this->expiries[$key] = $expiry;
- }
-
- return true;
- }
-
- private function normalizeTtl($ttl)
- {
- if (null === $ttl) {
- return $this->defaultLifetime;
- }
- if ($ttl instanceof \DateInterval) {
- $ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
- }
- if (\is_int($ttl)) {
- return 0 < $ttl ? $ttl : false;
- }
-
- throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
- }
-}
diff --git a/lib/symfony/cache/Simple/ChainCache.php b/lib/symfony/cache/Simple/ChainCache.php
deleted file mode 100644
index 2e6c7277d..000000000
--- a/lib/symfony/cache/Simple/ChainCache.php
+++ /dev/null
@@ -1,252 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Psr\SimpleCache\CacheInterface;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\ResettableInterface;
-
-/**
- * Chains several caches together.
- *
- * Cached items are fetched from the first cache having them in its data store.
- * They are saved and deleted in all caches at once.
- *
- * @author Nicolas Grekas
- */
-class ChainCache implements CacheInterface, PruneableInterface, ResettableInterface
-{
- private $miss;
- private $caches = [];
- private $defaultLifetime;
- private $cacheCount;
-
- /**
- * @param CacheInterface[] $caches The ordered list of caches used to fetch cached items
- * @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
- */
- public function __construct(array $caches, $defaultLifetime = 0)
- {
- if (!$caches) {
- throw new InvalidArgumentException('At least one cache must be specified.');
- }
-
- foreach ($caches as $cache) {
- if (!$cache instanceof CacheInterface) {
- throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), CacheInterface::class));
- }
- }
-
- $this->miss = new \stdClass();
- $this->caches = array_values($caches);
- $this->cacheCount = \count($this->caches);
- $this->defaultLifetime = 0 < $defaultLifetime ? (int) $defaultLifetime : null;
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- $miss = null !== $default && \is_object($default) ? $default : $this->miss;
-
- foreach ($this->caches as $i => $cache) {
- $value = $cache->get($key, $miss);
-
- if ($miss !== $value) {
- while (0 <= --$i) {
- $this->caches[$i]->set($key, $value, $this->defaultLifetime);
- }
-
- return $value;
- }
- }
-
- return $default;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- $miss = null !== $default && \is_object($default) ? $default : $this->miss;
-
- return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default);
- }
-
- private function generateItems($values, $cacheIndex, $miss, $default)
- {
- $missing = [];
- $nextCacheIndex = $cacheIndex + 1;
- $nextCache = isset($this->caches[$nextCacheIndex]) ? $this->caches[$nextCacheIndex] : null;
-
- foreach ($values as $k => $value) {
- if ($miss !== $value) {
- yield $k => $value;
- } elseif (!$nextCache) {
- yield $k => $default;
- } else {
- $missing[] = $k;
- }
- }
-
- if ($missing) {
- $cache = $this->caches[$cacheIndex];
- $values = $this->generateItems($nextCache->getMultiple($missing, $miss), $nextCacheIndex, $miss, $default);
-
- foreach ($values as $k => $value) {
- if ($miss !== $value) {
- $cache->set($k, $value, $this->defaultLifetime);
- yield $k => $value;
- } else {
- yield $k => $default;
- }
- }
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function has($key)
- {
- foreach ($this->caches as $cache) {
- if ($cache->has($key)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- $cleared = true;
- $i = $this->cacheCount;
-
- while ($i--) {
- $cleared = $this->caches[$i]->clear() && $cleared;
- }
-
- return $cleared;
- }
-
- /**
- * {@inheritdoc}
- */
- public function delete($key)
- {
- $deleted = true;
- $i = $this->cacheCount;
-
- while ($i--) {
- $deleted = $this->caches[$i]->delete($key) && $deleted;
- }
-
- return $deleted;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- if ($keys instanceof \Traversable) {
- $keys = iterator_to_array($keys, false);
- }
- $deleted = true;
- $i = $this->cacheCount;
-
- while ($i--) {
- $deleted = $this->caches[$i]->deleteMultiple($keys) && $deleted;
- }
-
- return $deleted;
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- $saved = true;
- $i = $this->cacheCount;
-
- while ($i--) {
- $saved = $this->caches[$i]->set($key, $value, $ttl) && $saved;
- }
-
- return $saved;
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- if ($values instanceof \Traversable) {
- $valuesIterator = $values;
- $values = function () use ($valuesIterator, &$values) {
- $generatedValues = [];
-
- foreach ($valuesIterator as $key => $value) {
- yield $key => $value;
- $generatedValues[$key] = $value;
- }
-
- $values = $generatedValues;
- };
- $values = $values();
- }
- $saved = true;
- $i = $this->cacheCount;
-
- while ($i--) {
- $saved = $this->caches[$i]->setMultiple($values, $ttl) && $saved;
- }
-
- return $saved;
- }
-
- /**
- * {@inheritdoc}
- */
- public function prune()
- {
- $pruned = true;
-
- foreach ($this->caches as $cache) {
- if ($cache instanceof PruneableInterface) {
- $pruned = $cache->prune() && $pruned;
- }
- }
-
- return $pruned;
- }
-
- /**
- * {@inheritdoc}
- */
- public function reset()
- {
- foreach ($this->caches as $cache) {
- if ($cache instanceof ResettableInterface) {
- $cache->reset();
- }
- }
- }
-}
diff --git a/lib/symfony/cache/Simple/DoctrineCache.php b/lib/symfony/cache/Simple/DoctrineCache.php
deleted file mode 100644
index ea1a4eda5..000000000
--- a/lib/symfony/cache/Simple/DoctrineCache.php
+++ /dev/null
@@ -1,31 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Doctrine\Common\Cache\CacheProvider;
-use Symfony\Component\Cache\Traits\DoctrineTrait;
-
-class DoctrineCache extends AbstractCache
-{
- use DoctrineTrait;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
- {
- parent::__construct('', $defaultLifetime);
- $this->provider = $provider;
- $provider->setNamespace($namespace);
- }
-}
diff --git a/lib/symfony/cache/Simple/FilesystemCache.php b/lib/symfony/cache/Simple/FilesystemCache.php
deleted file mode 100644
index ccd579534..000000000
--- a/lib/symfony/cache/Simple/FilesystemCache.php
+++ /dev/null
@@ -1,31 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\Traits\FilesystemTrait;
-
-class FilesystemCache extends AbstractCache implements PruneableInterface
-{
- use FilesystemTrait;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
- {
- parent::__construct('', $defaultLifetime);
- $this->init($namespace, $directory);
- }
-}
diff --git a/lib/symfony/cache/Simple/MemcachedCache.php b/lib/symfony/cache/Simple/MemcachedCache.php
deleted file mode 100644
index 94a9f297d..000000000
--- a/lib/symfony/cache/Simple/MemcachedCache.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Symfony\Component\Cache\Traits\MemcachedTrait;
-
-class MemcachedCache extends AbstractCache
-{
- use MemcachedTrait;
-
- protected $maxIdLength = 250;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
- {
- $this->init($client, $namespace, $defaultLifetime);
- }
-}
diff --git a/lib/symfony/cache/Simple/NullCache.php b/lib/symfony/cache/Simple/NullCache.php
deleted file mode 100644
index fa986aebd..000000000
--- a/lib/symfony/cache/Simple/NullCache.php
+++ /dev/null
@@ -1,86 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Psr\SimpleCache\CacheInterface;
-
-/**
- * @author Nicolas Grekas
- */
-class NullCache implements CacheInterface
-{
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- return $default;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- foreach ($keys as $key) {
- yield $key => $default;
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function has($key)
- {
- return false;
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function delete($key)
- {
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- return false;
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- return false;
- }
-}
diff --git a/lib/symfony/cache/Simple/PdoCache.php b/lib/symfony/cache/Simple/PdoCache.php
deleted file mode 100644
index c92e049a1..000000000
--- a/lib/symfony/cache/Simple/PdoCache.php
+++ /dev/null
@@ -1,51 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\Traits\PdoTrait;
-
-class PdoCache extends AbstractCache implements PruneableInterface
-{
- use PdoTrait;
-
- protected $maxIdLength = 255;
-
- /**
- * You can either pass an existing database connection as PDO instance or
- * a Doctrine DBAL Connection or a DSN string that will be used to
- * lazy-connect to the database when the cache is actually used.
- *
- * List of available options:
- * * db_table: The name of the table [default: cache_items]
- * * db_id_col: The column where to store the cache id [default: item_id]
- * * db_data_col: The column where to store the cache data [default: item_data]
- * * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
- * * db_time_col: The column where to store the timestamp [default: item_time]
- * * db_username: The username when lazy-connect [default: '']
- * * db_password: The password when lazy-connect [default: '']
- * * db_connection_options: An array of driver-specific connection options [default: []]
- *
- * @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
- * @param string $namespace
- * @param int $defaultLifetime
- * @param array $options An associative array of options
- *
- * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
- * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
- * @throws InvalidArgumentException When namespace contains invalid characters
- */
- public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
- {
- $this->init($connOrDsn, $namespace, $defaultLifetime, $options);
- }
-}
diff --git a/lib/symfony/cache/Simple/PhpArrayCache.php b/lib/symfony/cache/Simple/PhpArrayCache.php
deleted file mode 100644
index 7bb25ff80..000000000
--- a/lib/symfony/cache/Simple/PhpArrayCache.php
+++ /dev/null
@@ -1,259 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Psr\SimpleCache\CacheInterface;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\ResettableInterface;
-use Symfony\Component\Cache\Traits\PhpArrayTrait;
-
-/**
- * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
- * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
- *
- * @author Titouan Galopin
- */
-class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInterface
-{
- use PhpArrayTrait;
-
- /**
- * @param string $file The PHP file were values are cached
- * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
- */
- public function __construct($file, CacheInterface $fallbackPool)
- {
- $this->file = $file;
- $this->pool = $fallbackPool;
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
- }
-
- /**
- * This adapter should only be used on PHP 7.0+ to take advantage of how PHP
- * stores arrays in its latest versions. This factory method decorates the given
- * fallback pool with this adapter only if the current PHP version is supported.
- *
- * @param string $file The PHP file were values are cached
- * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
- *
- * @return CacheInterface
- */
- public static function create($file, CacheInterface $fallbackPool)
- {
- if (\PHP_VERSION_ID >= 70000) {
- return new static($file, $fallbackPool);
- }
-
- return $fallbackPool;
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
- if (null === $this->values) {
- $this->initialize();
- }
- if (!isset($this->values[$key])) {
- return $this->pool->get($key, $default);
- }
-
- $value = $this->values[$key];
-
- if ('N;' === $value) {
- $value = null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
- try {
- $e = null;
- $value = unserialize($value);
- } catch (\Error $e) {
- } catch (\Exception $e) {
- }
- if (null !== $e) {
- return $default;
- }
- }
-
- return $value;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- if ($keys instanceof \Traversable) {
- $keys = iterator_to_array($keys, false);
- } elseif (!\is_array($keys)) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
- foreach ($keys as $key) {
- if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
- }
- if (null === $this->values) {
- $this->initialize();
- }
-
- return $this->generateItems($keys, $default);
- }
-
- /**
- * {@inheritdoc}
- */
- public function has($key)
- {
- if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
- if (null === $this->values) {
- $this->initialize();
- }
-
- return isset($this->values[$key]) || $this->pool->has($key);
- }
-
- /**
- * {@inheritdoc}
- */
- public function delete($key)
- {
- if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
- if (null === $this->values) {
- $this->initialize();
- }
-
- return !isset($this->values[$key]) && $this->pool->delete($key);
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- if (!\is_array($keys) && !$keys instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
- }
-
- $deleted = true;
- $fallbackKeys = [];
-
- foreach ($keys as $key) {
- if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
-
- if (isset($this->values[$key])) {
- $deleted = false;
- } else {
- $fallbackKeys[] = $key;
- }
- }
- if (null === $this->values) {
- $this->initialize();
- }
-
- if ($fallbackKeys) {
- $deleted = $this->pool->deleteMultiple($fallbackKeys) && $deleted;
- }
-
- return $deleted;
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- if (!\is_string($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
- if (null === $this->values) {
- $this->initialize();
- }
-
- return !isset($this->values[$key]) && $this->pool->set($key, $value, $ttl);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- if (!\is_array($values) && !$values instanceof \Traversable) {
- throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
- }
-
- $saved = true;
- $fallbackValues = [];
-
- foreach ($values as $key => $value) {
- if (!\is_string($key) && !\is_int($key)) {
- throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
- }
-
- if (isset($this->values[$key])) {
- $saved = false;
- } else {
- $fallbackValues[$key] = $value;
- }
- }
-
- if ($fallbackValues) {
- $saved = $this->pool->setMultiple($fallbackValues, $ttl) && $saved;
- }
-
- return $saved;
- }
-
- private function generateItems(array $keys, $default)
- {
- $fallbackKeys = [];
-
- foreach ($keys as $key) {
- if (isset($this->values[$key])) {
- $value = $this->values[$key];
-
- if ('N;' === $value) {
- yield $key => null;
- } elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
- try {
- yield $key => unserialize($value);
- } catch (\Error $e) {
- yield $key => $default;
- } catch (\Exception $e) {
- yield $key => $default;
- }
- } else {
- yield $key => $value;
- }
- } else {
- $fallbackKeys[] = $key;
- }
- }
-
- if ($fallbackKeys) {
- foreach ($this->pool->getMultiple($fallbackKeys, $default) as $key => $item) {
- yield $key => $item;
- }
- }
- }
-}
diff --git a/lib/symfony/cache/Simple/PhpFilesCache.php b/lib/symfony/cache/Simple/PhpFilesCache.php
deleted file mode 100644
index 50c19034a..000000000
--- a/lib/symfony/cache/Simple/PhpFilesCache.php
+++ /dev/null
@@ -1,41 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Symfony\Component\Cache\Exception\CacheException;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\Traits\PhpFilesTrait;
-
-class PhpFilesCache extends AbstractCache implements PruneableInterface
-{
- use PhpFilesTrait;
-
- /**
- * @param string $namespace
- * @param int $defaultLifetime
- * @param string|null $directory
- *
- * @throws CacheException if OPcache is not enabled
- */
- public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
- {
- if (!static::isSupported()) {
- throw new CacheException('OPcache is not enabled.');
- }
- parent::__construct('', $defaultLifetime);
- $this->init($namespace, $directory);
-
- $e = new \Exception();
- $this->includeHandler = function () use ($e) { throw $e; };
- $this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
- }
-}
diff --git a/lib/symfony/cache/Simple/RedisCache.php b/lib/symfony/cache/Simple/RedisCache.php
deleted file mode 100644
index e82c0627e..000000000
--- a/lib/symfony/cache/Simple/RedisCache.php
+++ /dev/null
@@ -1,29 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Symfony\Component\Cache\Traits\RedisTrait;
-
-class RedisCache extends AbstractCache
-{
- use RedisTrait;
-
- /**
- * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
- * @param string $namespace
- * @param int $defaultLifetime
- */
- public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
- {
- $this->init($redisClient, $namespace, $defaultLifetime);
- }
-}
diff --git a/lib/symfony/cache/Simple/TraceableCache.php b/lib/symfony/cache/Simple/TraceableCache.php
deleted file mode 100644
index 61b22963e..000000000
--- a/lib/symfony/cache/Simple/TraceableCache.php
+++ /dev/null
@@ -1,241 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Simple;
-
-use Psr\SimpleCache\CacheInterface;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\ResettableInterface;
-
-/**
- * An adapter that collects data about all cache calls.
- *
- * @author Nicolas Grekas
- */
-class TraceableCache implements CacheInterface, PruneableInterface, ResettableInterface
-{
- private $pool;
- private $miss;
- private $calls = [];
-
- public function __construct(CacheInterface $pool)
- {
- $this->pool = $pool;
- $this->miss = new \stdClass();
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($key, $default = null)
- {
- $miss = null !== $default && \is_object($default) ? $default : $this->miss;
- $event = $this->start(__FUNCTION__);
- try {
- $value = $this->pool->get($key, $miss);
- } finally {
- $event->end = microtime(true);
- }
- if ($event->result[$key] = $miss !== $value) {
- ++$event->hits;
- } else {
- ++$event->misses;
- $value = $default;
- }
-
- return $value;
- }
-
- /**
- * {@inheritdoc}
- */
- public function has($key)
- {
- $event = $this->start(__FUNCTION__);
- try {
- return $event->result[$key] = $this->pool->has($key);
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function delete($key)
- {
- $event = $this->start(__FUNCTION__);
- try {
- return $event->result[$key] = $this->pool->delete($key);
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function set($key, $value, $ttl = null)
- {
- $event = $this->start(__FUNCTION__);
- try {
- return $event->result[$key] = $this->pool->set($key, $value, $ttl);
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function setMultiple($values, $ttl = null)
- {
- $event = $this->start(__FUNCTION__);
- $event->result['keys'] = [];
-
- if ($values instanceof \Traversable) {
- $values = function () use ($values, $event) {
- foreach ($values as $k => $v) {
- $event->result['keys'][] = $k;
- yield $k => $v;
- }
- };
- $values = $values();
- } elseif (\is_array($values)) {
- $event->result['keys'] = array_keys($values);
- }
-
- try {
- return $event->result['result'] = $this->pool->setMultiple($values, $ttl);
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMultiple($keys, $default = null)
- {
- $miss = null !== $default && \is_object($default) ? $default : $this->miss;
- $event = $this->start(__FUNCTION__);
- try {
- $result = $this->pool->getMultiple($keys, $miss);
- } finally {
- $event->end = microtime(true);
- }
- $f = function () use ($result, $event, $miss, $default) {
- $event->result = [];
- foreach ($result as $key => $value) {
- if ($event->result[$key] = $miss !== $value) {
- ++$event->hits;
- } else {
- ++$event->misses;
- $value = $default;
- }
- yield $key => $value;
- }
- };
-
- return $f();
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- $event = $this->start(__FUNCTION__);
- try {
- return $event->result = $this->pool->clear();
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteMultiple($keys)
- {
- $event = $this->start(__FUNCTION__);
- if ($keys instanceof \Traversable) {
- $keys = $event->result['keys'] = iterator_to_array($keys, false);
- } else {
- $event->result['keys'] = $keys;
- }
- try {
- return $event->result['result'] = $this->pool->deleteMultiple($keys);
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function prune()
- {
- if (!$this->pool instanceof PruneableInterface) {
- return false;
- }
- $event = $this->start(__FUNCTION__);
- try {
- return $event->result = $this->pool->prune();
- } finally {
- $event->end = microtime(true);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function reset()
- {
- if (!$this->pool instanceof ResettableInterface) {
- return;
- }
- $event = $this->start(__FUNCTION__);
- try {
- $this->pool->reset();
- } finally {
- $event->end = microtime(true);
- }
- }
-
- public function getCalls()
- {
- try {
- return $this->calls;
- } finally {
- $this->calls = [];
- }
- }
-
- private function start($name)
- {
- $this->calls[] = $event = new TraceableCacheEvent();
- $event->name = $name;
- $event->start = microtime(true);
-
- return $event;
- }
-}
-
-class TraceableCacheEvent
-{
- public $name;
- public $start;
- public $end;
- public $result;
- public $hits = 0;
- public $misses = 0;
-}
diff --git a/lib/symfony/cache/Traits/AbstractAdapterTrait.php b/lib/symfony/cache/Traits/AbstractAdapterTrait.php
new file mode 100644
index 000000000..f0173c422
--- /dev/null
+++ b/lib/symfony/cache/Traits/AbstractAdapterTrait.php
@@ -0,0 +1,424 @@
+
+ *
+ * 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\Cache\CacheItemInterface;
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait AbstractAdapterTrait
+{
+ use LoggerAwareTrait;
+
+ /**
+ * @var \Closure needs to be set by class, signature is function(string
- *
- * @internal
- */
-trait AbstractTrait
-{
- use LoggerAwareTrait;
-
- private $namespace;
- private $namespaceVersion = '';
- private $versioningIsEnabled = false;
- private $deferred = [];
-
- /**
- * @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.
- *
- * @param string $namespace The prefix used for all identifiers managed by this pool
- *
- * @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
- */
- abstract protected function doSave(array $values, $lifetime);
-
- /**
- * {@inheritdoc}
- */
- public function hasItem($key)
- {
- $id = $this->getId($key);
-
- if (isset($this->deferred[$key])) {
- $this->commit();
- }
-
- try {
- return $this->doHave($id);
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached', ['key' => $key, 'exception' => $e]);
-
- return false;
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- $this->deferred = [];
- if ($cleared = $this->versioningIsEnabled) {
- $namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5);
- try {
- $cleared = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
- } catch (\Exception $e) {
- $cleared = false;
- }
- if ($cleared = true === $cleared || [] === $cleared) {
- $this->namespaceVersion = $namespaceVersion;
- }
- }
-
- try {
- return $this->doClear($this->namespace) || $cleared;
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to clear the cache', ['exception' => $e]);
-
- return false;
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteItem($key)
- {
- return $this->deleteItems([$key]);
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteItems(array $keys)
- {
- $ids = [];
-
- 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;
- if ($this->doDelete([$id])) {
- continue;
- }
- } catch (\Exception $e) {
- }
- CacheItem::log($this->logger, 'Failed to delete key "{key}"', ['key' => $key, 'exception' => $e]);
- $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 = '';
-
- return $wasEnabled;
- }
-
- /**
- * {@inheritdoc}
- */
- public function reset()
- {
- if ($this->deferred) {
- $this->commit();
- }
- $this->namespaceVersion = '';
- }
-
- /**
- * Like the native unserialize() function but throws an exception if anything goes wrong.
- *
- * @param string $value
- *
- * @return mixed
- *
- * @throws \Exception
- */
- protected static function unserialize($value)
- {
- if ('b:0;' === $value) {
- return false;
- }
- $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
- try {
- if (false !== $value = unserialize($value)) {
- return $value;
- }
- throw new \DomainException('Failed to unserialize cached value.');
- } catch (\Error $e) {
- throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
- } finally {
- ini_set('unserialize_callback_func', $unserializeCallbackHandler);
- }
- }
-
- private function getId($key)
- {
- CacheItem::validateKey($key);
-
- if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
- $this->namespaceVersion = '1'.static::NS_SEPARATOR;
- try {
- foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
- $this->namespaceVersion = $v;
- }
- if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
- $this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), static::NS_SEPARATOR, 5);
- $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
- }
- } catch (\Exception $e) {
- }
- }
-
- if (null === $this->maxIdLength) {
- return $this->namespace.$this->namespaceVersion.$key;
- }
- if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
- $id = $this->namespace.$this->namespaceVersion.substr_replace(base64_encode(hash('sha256', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 22));
- }
-
- return $id;
- }
-
- /**
- * @internal
- */
- public static function handleUnserializeCallback($class)
- {
- throw new \DomainException('Class not found: '.$class);
- }
-}
diff --git a/lib/symfony/cache/Traits/ApcuTrait.php b/lib/symfony/cache/Traits/ApcuTrait.php
deleted file mode 100644
index 2f47f8e6d..000000000
--- a/lib/symfony/cache/Traits/ApcuTrait.php
+++ /dev/null
@@ -1,117 +0,0 @@
-
- *
- * 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 Symfony\Component\Cache\CacheItem;
-use Symfony\Component\Cache\Exception\CacheException;
-
-/**
- * @author Nicolas Grekas
- *
- * @internal
- */
-trait ApcuTrait
-{
- public static function isSupported()
- {
- return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN);
- }
-
- private function init($namespace, $defaultLifetime, $version)
- {
- if (!static::isSupported()) {
- throw new CacheException('APCu is not enabled.');
- }
- if ('cli' === \PHP_SAPI) {
- ini_set('apc.use_request_time', 0);
- }
- parent::__construct($namespace, $defaultLifetime);
-
- if (null !== $version) {
- CacheItem::validateKey($version);
-
- if (!apcu_exists($version.'@'.$namespace)) {
- $this->doClear($namespace);
- apcu_add($version.'@'.$namespace, null);
- }
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doFetch(array $ids)
- {
- try {
- foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
- if (null !== $v || $ok) {
- yield $k => $v;
- }
- }
- } catch (\Error $e) {
- throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doHave($id)
- {
- return apcu_exists($id);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doClear($namespace)
- {
- return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))
- ? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY))
- : apcu_clear_cache();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doDelete(array $ids)
- {
- foreach ($ids as $id) {
- apcu_delete($id);
- }
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doSave(array $values, $lifetime)
- {
- try {
- if (false === $failures = apcu_store($values, null, $lifetime)) {
- $failures = $values;
- }
-
- return array_keys($failures);
- } catch (\Error $e) {
- } catch (\Exception $e) {
- }
-
- if (1 === \count($values)) {
- // Workaround https://github.com/krakjoe/apcu/issues/170
- apcu_delete(key($values));
- }
-
- throw $e;
- }
-}
diff --git a/lib/symfony/cache/Traits/ArrayTrait.php b/lib/symfony/cache/Traits/ArrayTrait.php
deleted file mode 100644
index 0a60968e9..000000000
--- a/lib/symfony/cache/Traits/ArrayTrait.php
+++ /dev/null
@@ -1,108 +0,0 @@
-
- *
- * 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
- *
- * @internal
- */
-trait ArrayTrait
-{
- use LoggerAwareTrait;
-
- private $storeSerialized;
- private $values = [];
- private $expiries = [];
-
- /**
- * Returns all cached values, with cache miss as null.
- *
- * @return array
- */
- public function getValues()
- {
- return $this->values;
- }
-
- /**
- * {@inheritdoc}
- */
- public function hasItem($key)
- {
- CacheItem::validateKey($key);
-
- return isset($this->expiries[$key]) && ($this->expiries[$key] > time() || !$this->deleteItem($key));
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- $this->values = $this->expiries = [];
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function deleteItem($key)
- {
- CacheItem::validateKey($key);
-
- unset($this->values[$key], $this->expiries[$key]);
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- public function reset()
- {
- $this->clear();
- }
-
- private function generateItems(array $keys, $now, $f)
- {
- foreach ($keys as $i => $key) {
- try {
- if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
- $this->values[$key] = $value = null;
- } elseif (!$this->storeSerialized) {
- $value = $this->values[$key];
- } elseif ('b:0;' === $value = $this->values[$key]) {
- $value = false;
- } elseif (false === $value = unserialize($value)) {
- $this->values[$key] = $value = null;
- $isHit = false;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
- $this->values[$key] = $value = null;
- $isHit = false;
- }
- unset($keys[$i]);
-
- yield $key => $f($key, $value, $isHit);
- }
-
- foreach ($keys as $key) {
- yield $key => $f($key, null, false);
- }
- }
-}
diff --git a/lib/symfony/cache/Traits/ContractsTrait.php b/lib/symfony/cache/Traits/ContractsTrait.php
new file mode 100644
index 000000000..e4d48797f
--- /dev/null
+++ b/lib/symfony/cache/Traits/ContractsTrait.php
@@ -0,0 +1,109 @@
+
+ *
+ * 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\LoggerInterface;
+use Symfony\Component\Cache\Adapter\AdapterInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\LockRegistry;
+use Symfony\Contracts\Cache\CacheInterface;
+use Symfony\Contracts\Cache\CacheTrait;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait ContractsTrait
+{
+ use CacheTrait {
+ doGet as private contractsGet;
+ }
+
+ private $callbackWrapper;
+ private $computing = [];
+
+ /**
+ * Wraps the callback passed to ->get() in a callable.
+ *
+ * @return callable the previous callback wrapper
+ */
+ public function setCallbackWrapper(?callable $callbackWrapper): callable
+ {
+ if (!isset($this->callbackWrapper)) {
+ $this->callbackWrapper = \Closure::fromCallable([LockRegistry::class, 'compute']);;
+
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
+ $this->setCallbackWrapper(null);
+ }
+ }
+
+ $previousWrapper = $this->callbackWrapper;
+ $this->callbackWrapper = $callbackWrapper ?? static function (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) {
+ return $callback($item, $save);
+ };
+
+ return $previousWrapper;
+ }
+
+ private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, array &$metadata = null)
+ {
+ if (0 > $beta = $beta ?? 1.0) {
+ throw new InvalidArgumentException(sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta));
+ }
+
+ static $setMetadata;
+
+ $setMetadata ?? $setMetadata = \Closure::bind(
+ static function (CacheItem $item, float $startTime, ?array &$metadata) {
+ if ($item->expiry > $endTime = microtime(true)) {
+ $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
+ $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime));
+ } else {
+ unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME]);
+ }
+ },
+ null,
+ CacheItem::class
+ );
+
+ return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) {
+ // don't wrap nor save recursive calls
+ if (isset($this->computing[$key])) {
+ $value = $callback($item, $save);
+ $save = false;
+
+ return $value;
+ }
+
+ $this->computing[$key] = $key;
+ $startTime = microtime(true);
+
+ if (!isset($this->callbackWrapper)) {
+ $this->setCallbackWrapper($this->setCallbackWrapper(null));
+ }
+
+ try {
+ $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) {
+ $setMetadata($item, $startTime, $metadata);
+ }, $this->logger ?? null);
+ $setMetadata($item, $startTime, $metadata);
+
+ return $value;
+ } finally {
+ unset($this->computing[$key]);
+ }
+ }, $beta, $metadata, $this->logger ?? null);
+ }
+}
diff --git a/lib/symfony/cache/Traits/DoctrineTrait.php b/lib/symfony/cache/Traits/DoctrineTrait.php
deleted file mode 100644
index 48623e67c..000000000
--- a/lib/symfony/cache/Traits/DoctrineTrait.php
+++ /dev/null
@@ -1,98 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Cache\Traits;
-
-/**
- * @author Nicolas Grekas
- *
- * @internal
- */
-trait DoctrineTrait
-{
- private $provider;
-
- /**
- * {@inheritdoc}
- */
- public function reset()
- {
- parent::reset();
- $this->provider->setNamespace($this->provider->getNamespace());
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doFetch(array $ids)
- {
- $unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
- try {
- return $this->provider->fetchMultiple($ids);
- } catch (\Error $e) {
- $trace = $e->getTrace();
-
- if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
- switch ($trace[0]['function']) {
- case 'unserialize':
- case 'apcu_fetch':
- case 'apc_fetch':
- throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
- }
- }
-
- throw $e;
- } finally {
- ini_set('unserialize_callback_func', $unserializeCallbackHandler);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doHave($id)
- {
- return $this->provider->contains($id);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doClear($namespace)
- {
- $namespace = $this->provider->getNamespace();
-
- return isset($namespace[0])
- ? $this->provider->deleteAll()
- : $this->provider->flushAll();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doDelete(array $ids)
- {
- $ok = true;
- foreach ($ids as $id) {
- $ok = $this->provider->delete($id) && $ok;
- }
-
- return $ok;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doSave(array $values, $lifetime)
- {
- return $this->provider->saveMultiple($values, $lifetime);
- }
-}
diff --git a/lib/symfony/cache/Traits/FilesystemCommonTrait.php b/lib/symfony/cache/Traits/FilesystemCommonTrait.php
index 8071a382b..c06cc309a 100644
--- a/lib/symfony/cache/Traits/FilesystemCommonTrait.php
+++ b/lib/symfony/cache/Traits/FilesystemCommonTrait.php
@@ -23,10 +23,10 @@ trait FilesystemCommonTrait
private $directory;
private $tmp;
- private function init($namespace, $directory)
+ private function init(string $namespace, ?string $directory)
{
if (!isset($directory[0])) {
- $directory = sys_get_temp_dir().'/symfony-cache';
+ $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
} else {
$directory = realpath($directory) ?: $directory;
}
@@ -35,8 +35,10 @@ trait FilesystemCommonTrait
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
}
$directory .= \DIRECTORY_SEPARATOR.$namespace;
+ } else {
+ $directory .= \DIRECTORY_SEPARATOR.'@';
}
- if (!file_exists($directory)) {
+ if (!is_dir($directory)) {
@mkdir($directory, 0777, true);
}
$directory .= \DIRECTORY_SEPARATOR;
@@ -51,12 +53,16 @@ trait FilesystemCommonTrait
/**
* {@inheritdoc}
*/
- protected function doClear($namespace)
+ protected function doClear(string $namespace)
{
$ok = true;
- foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
- $ok = ($file->isDir() || @unlink($file) || !file_exists($file)) && $ok;
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ if ('' !== $namespace && !str_starts_with($this->getFileKey($file), $namespace)) {
+ continue;
+ }
+
+ $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
}
return $ok;
@@ -71,23 +77,39 @@ trait FilesystemCommonTrait
foreach ($ids as $id) {
$file = $this->getFile($id);
- $ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok;
+ $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
}
return $ok;
}
- private function write($file, $data, $expiresAt = null)
+ protected function doUnlink(string $file)
+ {
+ return @unlink($file);
+ }
+
+ private function write(string $file, string $data, int $expiresAt = null)
{
set_error_handler(__CLASS__.'::throwError');
try {
if (null === $this->tmp) {
- $this->tmp = $this->directory.uniqid('', true);
+ $this->tmp = $this->directory.bin2hex(random_bytes(6));
}
- file_put_contents($this->tmp, $data);
+ try {
+ $h = fopen($this->tmp, 'x');
+ } catch (\ErrorException $e) {
+ if (!str_contains($e->getMessage(), 'File exists')) {
+ throw $e;
+ }
+
+ $this->tmp = $this->directory.bin2hex(random_bytes(6));
+ $h = fopen($this->tmp, 'x');
+ }
+ fwrite($h, $data);
+ fclose($h);
if (null !== $expiresAt) {
- touch($this->tmp, $expiresAt);
+ touch($this->tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds
}
return rename($this->tmp, $file);
@@ -96,26 +118,62 @@ trait FilesystemCommonTrait
}
}
- private function getFile($id, $mkdir = false)
+ private function getFile(string $id, bool $mkdir = false, string $directory = null)
{
- $hash = str_replace('/', '-', base64_encode(hash('sha256', static::class.$id, true)));
- $dir = $this->directory.strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
+ // Use MD5 to favor speed over security, which is not an issue here
+ $hash = str_replace('/', '-', base64_encode(hash('md5', static::class.$id, true)));
+ $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
- if ($mkdir && !file_exists($dir)) {
+ if ($mkdir && !is_dir($dir)) {
@mkdir($dir, 0777, true);
}
return $dir.substr($hash, 2, 20);
}
+ private function getFileKey(string $file): string
+ {
+ return '';
+ }
+
+ private function scanHashDir(string $directory): \Generator
+ {
+ if (!is_dir($directory)) {
+ return;
+ }
+
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for ($i = 0; $i < 38; ++$i) {
+ if (!is_dir($directory.$chars[$i])) {
+ continue;
+ }
+
+ for ($j = 0; $j < 38; ++$j) {
+ if (!is_dir($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
+ continue;
+ }
+
+ foreach (@scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) {
+ if ('.' !== $file && '..' !== $file) {
+ yield $dir.\DIRECTORY_SEPARATOR.$file;
+ }
+ }
+ }
+ }
+ }
+
/**
* @internal
*/
- public static function throwError($type, $message, $file, $line)
+ public static function throwError(int $type, string $message, string $file, int $line)
{
throw new \ErrorException($message, 0, $type, $file, $line);
}
+ /**
+ * @return array
+ */
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
@@ -131,7 +189,7 @@ trait FilesystemCommonTrait
if (method_exists(parent::class, '__destruct')) {
parent::__destruct();
}
- if (null !== $this->tmp && file_exists($this->tmp)) {
+ if (null !== $this->tmp && is_file($this->tmp)) {
unlink($this->tmp);
}
}
diff --git a/lib/symfony/cache/Traits/FilesystemTrait.php b/lib/symfony/cache/Traits/FilesystemTrait.php
index 9d7f55784..38b741f1c 100644
--- a/lib/symfony/cache/Traits/FilesystemTrait.php
+++ b/lib/symfony/cache/Traits/FilesystemTrait.php
@@ -23,6 +23,8 @@ trait FilesystemTrait
{
use FilesystemCommonTrait;
+ private $marshaller;
+
/**
* @return bool
*/
@@ -31,8 +33,8 @@ trait FilesystemTrait
$time = time();
$pruned = true;
- foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
- if (!$h = @fopen($file, 'rb')) {
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ if (!$h = @fopen($file, 'r')) {
continue;
}
@@ -57,7 +59,7 @@ trait FilesystemTrait
foreach ($ids as $id) {
$file = $this->getFile($id);
- if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
+ if (!is_file($file) || !$h = @fopen($file, 'r')) {
continue;
}
if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) {
@@ -68,7 +70,7 @@ trait FilesystemTrait
$value = stream_get_contents($h);
fclose($h);
if ($i === $id) {
- $values[$id] = parent::unserialize($value);
+ $values[$id] = $this->marshaller->unmarshall($value);
}
}
}
@@ -79,29 +81,44 @@ trait FilesystemTrait
/**
* {@inheritdoc}
*/
- protected function doHave($id)
+ protected function doHave(string $id)
{
$file = $this->getFile($id);
- return file_exists($file) && (@filemtime($file) > time() || $this->doFetch([$id]));
+ return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id]));
}
/**
* {@inheritdoc}
*/
- protected function doSave(array $values, $lifetime)
+ protected function doSave(array $values, int $lifetime)
{
- $ok = true;
$expiresAt = $lifetime ? (time() + $lifetime) : 0;
+ $values = $this->marshaller->marshall($values, $failed);
foreach ($values as $id => $value) {
- $ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;
+ if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
+ $failed[] = $id;
+ }
}
- if (!$ok && !is_writable($this->directory)) {
+ if ($failed && !is_writable($this->directory)) {
throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory));
}
- return $ok;
+ return $failed;
+ }
+
+ private function getFileKey(string $file): string
+ {
+ if (!$h = @fopen($file, 'r')) {
+ return '';
+ }
+
+ fgets($h); // expiry
+ $encodedKey = fgets($h);
+ fclose($h);
+
+ return rawurldecode(rtrim($encodedKey));
}
}
diff --git a/lib/symfony/cache/Traits/MemcachedTrait.php b/lib/symfony/cache/Traits/MemcachedTrait.php
deleted file mode 100644
index 34d0208ef..000000000
--- a/lib/symfony/cache/Traits/MemcachedTrait.php
+++ /dev/null
@@ -1,297 +0,0 @@
-
- *
- * 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 Symfony\Component\Cache\Exception\CacheException;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-
-/**
- * @author Rob Frawley 2nd
- *
- * @internal
- */
-trait MemcachedTrait
-{
- private static $defaultClientOptions = [
- 'persistent_id' => null,
- 'username' => null,
- 'password' => null,
- \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
- ];
-
- private $client;
- private $lazyClient;
-
- public static function isSupported()
- {
- return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
- }
-
- private function init(\Memcached $client, $namespace, $defaultLifetime)
- {
- if (!static::isSupported()) {
- throw new CacheException('Memcached >= 2.2.0 is required.');
- }
- if ('Memcached' === \get_class($client)) {
- $opt = $client->getOption(\Memcached::OPT_SERIALIZER);
- if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
- throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
- }
- $this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
- $this->client = $client;
- } else {
- $this->lazyClient = $client;
- }
-
- parent::__construct($namespace, $defaultLifetime);
- $this->enableVersioning();
- }
-
- /**
- * Creates a Memcached instance.
- *
- * By default, the binary protocol, no block, and libketama compatible options are enabled.
- *
- * Examples for servers:
- * - 'memcached://user:pass@localhost?weight=33'
- * - [['localhost', 11211, 33]]
- *
- * @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
- * @param array $options An array of options
- *
- * @return \Memcached
- *
- * @throws \ErrorException When invalid options or servers are provided
- */
- public static function createConnection($servers, array $options = [])
- {
- if (\is_string($servers)) {
- $servers = [$servers];
- } elseif (!\is_array($servers)) {
- throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, "%s" given.', \gettype($servers)));
- }
- if (!static::isSupported()) {
- throw new CacheException('Memcached >= 2.2.0 is required.');
- }
- set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
- try {
- $options += static::$defaultClientOptions;
- $client = new \Memcached($options['persistent_id']);
- $username = $options['username'];
- $password = $options['password'];
-
- // parse any DSN in $servers
- foreach ($servers as $i => $dsn) {
- if (\is_array($dsn)) {
- continue;
- }
- if (0 !== strpos($dsn, 'memcached://')) {
- throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s" does not start with "memcached://".', $dsn));
- }
- $params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
- if (!empty($m[1])) {
- list($username, $password) = explode(':', $m[1], 2) + [1 => null];
- }
-
- return 'file://';
- }, $dsn);
- if (false === $params = parse_url($params)) {
- throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
- }
- if (!isset($params['host']) && !isset($params['path'])) {
- throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: "%s".', $dsn));
- }
- if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
- $params['weight'] = $m[1];
- $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
- }
- $params += [
- 'host' => isset($params['host']) ? $params['host'] : $params['path'],
- 'port' => isset($params['host']) ? 11211 : null,
- 'weight' => 0,
- ];
- if (isset($params['query'])) {
- parse_str($params['query'], $query);
- $params += $query;
- $options = $query + $options;
- }
-
- $servers[$i] = [$params['host'], $params['port'], $params['weight']];
- }
-
- // set client's options
- unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
- $options = array_change_key_case($options, \CASE_UPPER);
- $client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
- $client->setOption(\Memcached::OPT_NO_BLOCK, true);
- $client->setOption(\Memcached::OPT_TCP_NODELAY, true);
- if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
- $client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
- }
- foreach ($options as $name => $value) {
- if (\is_int($name)) {
- continue;
- }
- if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
- $value = \constant('Memcached::'.$name.'_'.strtoupper($value));
- }
- $opt = \constant('Memcached::OPT_'.$name);
-
- unset($options[$name]);
- $options[$opt] = $value;
- }
- $client->setOptions($options);
-
- // set client's servers, taking care of persistent connections
- if (!$client->isPristine()) {
- $oldServers = [];
- foreach ($client->getServerList() as $server) {
- $oldServers[] = [$server['host'], $server['port']];
- }
-
- $newServers = [];
- foreach ($servers as $server) {
- if (1 < \count($server)) {
- $server = array_values($server);
- unset($server[2]);
- $server[1] = (int) $server[1];
- }
- $newServers[] = $server;
- }
-
- if ($oldServers !== $newServers) {
- $client->resetServerList();
- $client->addServers($servers);
- }
- } else {
- $client->addServers($servers);
- }
-
- if (null !== $username || null !== $password) {
- if (!method_exists($client, 'setSaslAuthData')) {
- trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
- }
- $client->setSaslAuthData($username, $password);
- }
-
- return $client;
- } finally {
- restore_error_handler();
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doSave(array $values, $lifetime)
- {
- if ($lifetime && $lifetime > 30 * 86400) {
- $lifetime += time();
- }
-
- $encodedValues = [];
- foreach ($values as $key => $value) {
- $encodedValues[rawurlencode($key)] = $value;
- }
-
- return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime));
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doFetch(array $ids)
- {
- $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
- try {
- $encodedIds = array_map('rawurlencode', $ids);
-
- $encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
-
- $result = [];
- foreach ($encodedResult as $key => $value) {
- $result[rawurldecode($key)] = $value;
- }
-
- return $result;
- } catch (\Error $e) {
- throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
- } finally {
- ini_set('unserialize_callback_func', $unserializeCallbackHandler);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doHave($id)
- {
- return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doDelete(array $ids)
- {
- $ok = true;
- $encodedIds = array_map('rawurlencode', $ids);
- foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
- if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
- $ok = false;
- break;
- }
- }
-
- return $ok;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doClear($namespace)
- {
- return '' === $namespace && $this->getClient()->flush();
- }
-
- private function checkResultCode($result)
- {
- $code = $this->client->getResultCode();
-
- if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
- return $result;
- }
-
- throw new CacheException('MemcachedAdapter client error: '.strtolower($this->client->getResultMessage()));
- }
-
- /**
- * @return \Memcached
- */
- private function getClient()
- {
- if ($this->client) {
- return $this->client;
- }
-
- $opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
- if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
- throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
- }
- if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
- throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
- }
-
- return $this->client = $this->lazyClient;
- }
-}
diff --git a/lib/symfony/cache/Traits/PdoTrait.php b/lib/symfony/cache/Traits/PdoTrait.php
deleted file mode 100644
index 917e8dd13..000000000
--- a/lib/symfony/cache/Traits/PdoTrait.php
+++ /dev/null
@@ -1,435 +0,0 @@
-
- *
- * 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 Doctrine\DBAL\Connection;
-use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
-use Doctrine\DBAL\Schema\Schema;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-
-/**
- * @internal
- */
-trait PdoTrait
-{
- private $conn;
- private $dsn;
- private $driver;
- private $serverVersion;
- private $table = 'cache_items';
- private $idCol = 'item_id';
- private $dataCol = 'item_data';
- private $lifetimeCol = 'item_lifetime';
- private $timeCol = 'item_time';
- private $username = '';
- private $password = '';
- private $connectionOptions = [];
- private $namespace;
-
- private function init($connOrDsn, $namespace, $defaultLifetime, array $options)
- {
- if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
- throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
- }
-
- if ($connOrDsn instanceof \PDO) {
- if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
- throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)).', __CLASS__));
- }
-
- $this->conn = $connOrDsn;
- } elseif ($connOrDsn instanceof Connection) {
- $this->conn = $connOrDsn;
- } elseif (\is_string($connOrDsn)) {
- $this->dsn = $connOrDsn;
- } else {
- throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn)));
- }
-
- $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
- $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
- $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
- $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
- $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
- $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
- $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
- $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
- $this->namespace = $namespace;
-
- parent::__construct($namespace, $defaultLifetime);
- }
-
- /**
- * Creates the table to store cache items which can be called once for setup.
- *
- * Cache ID are saved in a column of maximum length 255. Cache data is
- * saved in a BLOB.
- *
- * @throws \PDOException When the table already exists
- * @throws DBALException When the table already exists
- * @throws \DomainException When an unsupported PDO driver is used
- */
- public function createTable()
- {
- // connect if we are not yet
- $conn = $this->getConnection();
-
- if ($conn instanceof Connection) {
- $types = [
- 'mysql' => 'binary',
- 'sqlite' => 'text',
- 'pgsql' => 'string',
- 'oci' => 'string',
- 'sqlsrv' => 'string',
- ];
- if (!isset($types[$this->driver])) {
- throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
- }
-
- $schema = new Schema();
- $table = $schema->createTable($this->table);
- $table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
- $table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
- $table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
- $table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
- $table->setPrimaryKey([$this->idCol]);
-
- foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
- if (method_exists($conn, 'executeStatement')) {
- $conn->executeStatement($sql);
- } else {
- $conn->exec($sql);
- }
- }
-
- return;
- }
-
- switch ($this->driver) {
- case 'mysql':
- // We use varbinary for the ID column because it prevents unwanted conversions:
- // - character set conversions between server and client
- // - trailing space removal
- // - case-insensitivity
- // - language processing like é == e
- $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
- break;
- case 'sqlite':
- $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
- break;
- case 'pgsql':
- $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
- break;
- case 'oci':
- $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
- break;
- case 'sqlsrv':
- $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
- break;
- default:
- throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
- }
-
- if (method_exists($conn, 'executeStatement')) {
- $conn->executeStatement($sql);
- } else {
- $conn->exec($sql);
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function prune()
- {
- $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
-
- if ('' !== $this->namespace) {
- $deleteSql .= " AND $this->idCol LIKE :namespace";
- }
-
- $delete = $this->getConnection()->prepare($deleteSql);
- $delete->bindValue(':time', time(), \PDO::PARAM_INT);
-
- if ('' !== $this->namespace) {
- $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
- }
-
- return $delete->execute();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doFetch(array $ids)
- {
- $now = time();
- $expired = [];
-
- $sql = str_pad('', (\count($ids) << 1) - 1, '?,');
- $sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)";
- $stmt = $this->getConnection()->prepare($sql);
- $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
- foreach ($ids as $id) {
- $stmt->bindValue(++$i, $id);
- }
- $result = $stmt->execute();
-
- if (\is_object($result)) {
- $result = $result->iterateNumeric();
- } else {
- $stmt->setFetchMode(\PDO::FETCH_NUM);
- $result = $stmt;
- }
-
- foreach ($result as $row) {
- if (null === $row[1]) {
- $expired[] = $row[0];
- } else {
- yield $row[0] => parent::unserialize(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
- }
- }
-
- if ($expired) {
- $sql = str_pad('', (\count($expired) << 1) - 1, '?,');
- $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)";
- $stmt = $this->getConnection()->prepare($sql);
- $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
- foreach ($expired as $id) {
- $stmt->bindValue(++$i, $id);
- }
- $stmt->execute();
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doHave($id)
- {
- $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
- $stmt = $this->getConnection()->prepare($sql);
-
- $stmt->bindValue(':id', $id);
- $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
- $result = $stmt->execute();
-
- return (bool) (\is_object($result) ? $result->fetchOne() : $stmt->fetchColumn());
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doClear($namespace)
- {
- $conn = $this->getConnection();
-
- if ('' === $namespace) {
- if ('sqlite' === $this->driver) {
- $sql = "DELETE FROM $this->table";
- } else {
- $sql = "TRUNCATE TABLE $this->table";
- }
- } else {
- $sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
- }
-
- if (method_exists($conn, 'executeStatement')) {
- $conn->executeStatement($sql);
- } else {
- $conn->exec($sql);
- }
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doDelete(array $ids)
- {
- $sql = str_pad('', (\count($ids) << 1) - 1, '?,');
- $sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
- $stmt = $this->getConnection()->prepare($sql);
- $stmt->execute(array_values($ids));
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doSave(array $values, $lifetime)
- {
- $serialized = [];
- $failed = [];
-
- foreach ($values as $id => $value) {
- try {
- $serialized[$id] = serialize($value);
- } catch (\Exception $e) {
- $failed[] = $id;
- }
- }
-
- if (!$serialized) {
- return $failed;
- }
-
- $conn = $this->getConnection();
- $driver = $this->driver;
- $insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
-
- switch (true) {
- case 'mysql' === $driver:
- $sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
- break;
- case 'oci' === $driver:
- // DUAL is Oracle specific dummy table
- $sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
- "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
- "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
- break;
- case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='):
- // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
- // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
- $sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
- "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
- "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
- break;
- case 'sqlite' === $driver:
- $sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
- break;
- case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='):
- $sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
- break;
- default:
- $driver = null;
- $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
- break;
- }
-
- $now = time();
- $lifetime = $lifetime ?: null;
- $stmt = $conn->prepare($sql);
-
- if ('sqlsrv' === $driver || 'oci' === $driver) {
- $stmt->bindParam(1, $id);
- $stmt->bindParam(2, $id);
- $stmt->bindParam(3, $data, \PDO::PARAM_LOB);
- $stmt->bindValue(4, $lifetime, \PDO::PARAM_INT);
- $stmt->bindValue(5, $now, \PDO::PARAM_INT);
- $stmt->bindParam(6, $data, \PDO::PARAM_LOB);
- $stmt->bindValue(7, $lifetime, \PDO::PARAM_INT);
- $stmt->bindValue(8, $now, \PDO::PARAM_INT);
- } else {
- $stmt->bindParam(':id', $id);
- $stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
- $stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
- $stmt->bindValue(':time', $now, \PDO::PARAM_INT);
- }
- if (null === $driver) {
- $insertStmt = $conn->prepare($insertSql);
-
- $insertStmt->bindParam(':id', $id);
- $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
- $insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
- $insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
- }
-
- foreach ($serialized as $id => $data) {
- $result = $stmt->execute();
-
- if (null === $driver && !(\is_object($result) ? $result->rowCount() : $stmt->rowCount())) {
- try {
- $insertStmt->execute();
- } catch (DBALException $e) {
- } catch (\PDOException $e) {
- // A concurrent write won, let it be
- }
- }
- }
-
- return $failed;
- }
-
- /**
- * @return \PDO|Connection
- */
- private function getConnection()
- {
- if (null === $this->conn) {
- $this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
- $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
- }
- if (null === $this->driver) {
- if ($this->conn instanceof \PDO) {
- $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
- } else {
- $driver = $this->conn->getDriver();
-
- switch (true) {
- case $driver instanceof \Doctrine\DBAL\Driver\AbstractMySQLDriver:
- case $driver instanceof \Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\Mysqli\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDOMySql\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDO\MySQL\Driver:
- $this->driver = 'mysql';
- break;
- case $driver instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDO\SQLite\Driver:
- $this->driver = 'sqlite';
- break;
- case $driver instanceof \Doctrine\DBAL\Driver\PDOPgSql\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDO\PgSQL\Driver:
- $this->driver = 'pgsql';
- break;
- case $driver instanceof \Doctrine\DBAL\Driver\OCI8\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDOOracle\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDO\OCI\Driver:
- $this->driver = 'oci';
- break;
- case $driver instanceof \Doctrine\DBAL\Driver\SQLSrv\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDOSqlsrv\Driver:
- case $driver instanceof \Doctrine\DBAL\Driver\PDO\SQLSrv\Driver:
- $this->driver = 'sqlsrv';
- break;
- default:
- $this->driver = \get_class($driver);
- break;
- }
- }
- }
-
- return $this->conn;
- }
-
- /**
- * @return string
- */
- private function getServerVersion()
- {
- if (null === $this->serverVersion) {
- $conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection();
- if ($conn instanceof \PDO) {
- $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
- } elseif ($conn instanceof ServerInfoAwareConnection) {
- $this->serverVersion = $conn->getServerVersion();
- } else {
- $this->serverVersion = '0';
- }
- }
-
- return $this->serverVersion;
- }
-}
diff --git a/lib/symfony/cache/Traits/PhpArrayTrait.php b/lib/symfony/cache/Traits/PhpArrayTrait.php
deleted file mode 100644
index 972c75121..000000000
--- a/lib/symfony/cache/Traits/PhpArrayTrait.php
+++ /dev/null
@@ -1,152 +0,0 @@
-
- *
- * 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 Symfony\Component\Cache\CacheItem;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-
-/**
- * @author Titouan Galopin
- *
- * @internal
- */
-trait PhpArrayTrait
-{
- use ProxyTrait;
-
- private $file;
- private $values;
- private $zendDetectUnicode;
-
- private static $valuesCache = [];
-
- /**
- * Store an array of cached values.
- *
- * @param array $values The cached values
- */
- public function warmUp(array $values)
- {
- if (file_exists($this->file)) {
- if (!is_file($this->file)) {
- throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: "%s".', $this->file));
- }
-
- if (!is_writable($this->file)) {
- throw new InvalidArgumentException(sprintf('Cache file is not writable: "%s".', $this->file));
- }
- } else {
- $directory = \dirname($this->file);
-
- if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
- throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: "%s".', $directory));
- }
-
- if (!is_writable($directory)) {
- throw new InvalidArgumentException(sprintf('Cache directory is not writable: "%s".', $directory));
- }
- }
-
- $dump = <<<'EOF'
- $value) {
- CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
-
- if (null === $value || \is_object($value)) {
- try {
- $value = serialize($value);
- } catch (\Exception $e) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \get_class($value)), 0, $e);
- }
- } elseif (\is_array($value)) {
- try {
- $serialized = serialize($value);
- $unserialized = unserialize($serialized);
- } catch (\Exception $e) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e);
- }
- // Store arrays serialized if they contain any objects or references
- if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
- $value = $serialized;
- }
- } elseif (\is_string($value)) {
- // Serialize strings if they could be confused with serialized objects or arrays
- if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
- $value = serialize($value);
- }
- } elseif (!is_scalar($value)) {
- throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable "%s" value.', $key, \gettype($value)));
- }
-
- $dump .= var_export($key, true).' => '.var_export($value, true).",\n";
- }
-
- $dump .= "\n];\n";
- $dump = str_replace("' . \"\\0\" . '", "\0", $dump);
-
- $tmpFile = uniqid($this->file, true);
-
- file_put_contents($tmpFile, $dump);
- @chmod($tmpFile, 0666 & ~umask());
- unset($serialized, $unserialized, $value, $dump);
-
- @rename($tmpFile, $this->file);
- unset(self::$valuesCache[$this->file]);
-
- $this->initialize();
- }
-
- /**
- * {@inheritdoc}
- */
- public function clear()
- {
- $this->values = [];
-
- $cleared = @unlink($this->file) || !file_exists($this->file);
- unset(self::$valuesCache[$this->file]);
-
- return $this->pool->clear() && $cleared;
- }
-
- /**
- * Load the cache file.
- */
- private function initialize()
- {
- if (isset(self::$valuesCache[$this->file])) {
- $this->values = self::$valuesCache[$this->file];
-
- return;
- }
-
- if ($this->zendDetectUnicode) {
- $zmb = ini_set('zend.detect_unicode', 0);
- }
- try {
- $this->values = self::$valuesCache[$this->file] = file_exists($this->file) ? (include $this->file ?: []) : [];
- } finally {
- if ($this->zendDetectUnicode) {
- ini_set('zend.detect_unicode', $zmb);
- }
- }
- }
-}
diff --git a/lib/symfony/cache/Traits/PhpFilesTrait.php b/lib/symfony/cache/Traits/PhpFilesTrait.php
deleted file mode 100644
index 2668b26c1..000000000
--- a/lib/symfony/cache/Traits/PhpFilesTrait.php
+++ /dev/null
@@ -1,158 +0,0 @@
-
- *
- * 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 Symfony\Component\Cache\Exception\CacheException;
-use Symfony\Component\Cache\Exception\InvalidArgumentException;
-
-/**
- * @author Piotr Stankowski
- * @author Rob Frawley 2nd
@@ -36,7 +36,7 @@ trait ProxyTrait
*/
public function reset()
{
- if ($this->pool instanceof ResettableInterface) {
+ if ($this->pool instanceof ResetInterface) {
$this->pool->reset();
}
}
diff --git a/lib/symfony/cache/Traits/RedisClusterNodeProxy.php b/lib/symfony/cache/Traits/RedisClusterNodeProxy.php
new file mode 100644
index 000000000..deba74f6a
--- /dev/null
+++ b/lib/symfony/cache/Traits/RedisClusterNodeProxy.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Traits;
+
+/**
+ * This file acts as a wrapper to the \RedisCluster implementation so it can accept the same type of calls as
+ * individual \Redis objects.
+ *
+ * Calls are made to individual nodes via: RedisCluster->{method}($host, ...args)'
+ * according to https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands
+ *
+ * @author Jack Thomas
+ *
+ * @final
*/
-class ComposerResource implements SelfCheckingResourceInterface, \Serializable
+class ComposerResource implements SelfCheckingResourceInterface
{
private $vendors;
@@ -28,15 +30,12 @@ class ComposerResource implements SelfCheckingResourceInterface, \Serializable
$this->vendors = self::$runtimeVendors;
}
- public function getVendors()
+ public function getVendors(): array
{
return array_keys($this->vendors);
}
- /**
- * {@inheritdoc}
- */
- public function __toString()
+ public function __toString(): string
{
return __CLASS__;
}
@@ -44,38 +43,22 @@ class ComposerResource implements SelfCheckingResourceInterface, \Serializable
/**
* {@inheritdoc}
*/
- public function isFresh($timestamp)
+ public function isFresh(int $timestamp): bool
{
self::refresh();
return array_values(self::$runtimeVendors) === array_values($this->vendors);
}
- /**
- * @internal
- */
- public function serialize()
- {
- return serialize($this->vendors);
- }
-
- /**
- * @internal
- */
- public function unserialize($serialized)
- {
- $this->vendors = unserialize($serialized);
- }
-
private static function refresh()
{
self::$runtimeVendors = [];
foreach (get_declared_classes() as $class) {
- if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
+ if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
- $v = \dirname(\dirname($r->getFileName()));
- if (file_exists($v.'/composer/installed.json')) {
+ $v = \dirname($r->getFileName(), 2);
+ if (is_file($v.'/composer/installed.json')) {
self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
}
}
diff --git a/lib/symfony/config/Resource/DirectoryResource.php b/lib/symfony/config/Resource/DirectoryResource.php
index e79b19ec2..035814a2a 100644
--- a/lib/symfony/config/Resource/DirectoryResource.php
+++ b/lib/symfony/config/Resource/DirectoryResource.php
@@ -15,8 +15,10 @@ namespace Symfony\Component\Config\Resource;
* DirectoryResource represents a resources stored in a subdirectory tree.
*
* @author Fabien Potencier
+ *
+ * @final
+ *
+ * @implements \IteratorAggregate
+ *
+ * @final
*/
-class ReflectionClassResource implements SelfCheckingResourceInterface, \Serializable
+class ReflectionClassResource implements SelfCheckingResourceInterface
{
private $files = [];
private $className;
@@ -25,14 +28,17 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
private $excludedVendors = [];
private $hash;
- public function __construct(\ReflectionClass $classReflector, $excludedVendors = [])
+ public function __construct(\ReflectionClass $classReflector, array $excludedVendors = [])
{
$this->className = $classReflector->name;
$this->classReflector = $classReflector;
$this->excludedVendors = $excludedVendors;
}
- public function isFresh($timestamp)
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
@@ -52,7 +58,7 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
return true;
}
- public function __toString()
+ public function __toString(): string
{
return 'reflection.'.$this->className;
}
@@ -60,22 +66,14 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
/**
* @internal
*/
- public function serialize()
+ public function __sleep(): array
{
if (null === $this->hash) {
$this->hash = $this->computeHash();
$this->loadFiles($this->classReflector);
}
- return serialize([$this->files, $this->className, $this->hash]);
- }
-
- /**
- * @internal
- */
- public function unserialize($serialized)
- {
- list($this->files, $this->className, $this->hash) = unserialize($serialized);
+ return ['files', 'className', 'hash'];
}
private function loadFiles(\ReflectionClass $class)
@@ -85,9 +83,9 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
}
do {
$file = $class->getFileName();
- if (false !== $file && file_exists($file)) {
+ if (false !== $file && is_file($file)) {
foreach ($this->excludedVendors as $vendor) {
- if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
+ if (str_starts_with($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
$file = false;
break;
}
@@ -102,7 +100,7 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
} while ($class = $class->getParentClass());
}
- private function computeHash()
+ private function computeHash(): string
{
if (null === $this->classReflector) {
try {
@@ -121,8 +119,17 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
return hash_final($hash);
}
- private function generateSignature(\ReflectionClass $class)
+ private function generateSignature(\ReflectionClass $class): iterable
{
+ if (\PHP_VERSION_ID >= 80000) {
+ $attributes = [];
+ foreach ($class->getAttributes() as $a) {
+ $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()];
+ }
+ yield print_r($attributes, true);
+ $attributes = [];
+ }
+
yield $class->getDocComment();
yield (int) $class->isFinal();
yield (int) $class->isAbstract();
@@ -139,6 +146,14 @@ class ReflectionClassResource implements SelfCheckingResourceInterface, \Seriali
$defaults = $class->getDefaultProperties();
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) {
+ if (\PHP_VERSION_ID >= 80000) {
+ foreach ($p->getAttributes() as $a) {
+ $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()];
+ }
+ yield print_r($attributes, true);
+ $attributes = [];
+ }
+
yield $p->getDocComment();
yield $p->isDefault() ? '
+ */
+final class LazyCommand extends Command
+{
+ private $command;
+ private $isEnabled;
+
+ public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
+ {
+ $this->setName($name)
+ ->setAliases($aliases)
+ ->setHidden($isHidden)
+ ->setDescription($description);
+
+ $this->command = $commandFactory;
+ $this->isEnabled = $isEnabled;
+ }
+
+ public function ignoreValidationErrors(): void
+ {
+ $this->getCommand()->ignoreValidationErrors();
+ }
+
+ public function setApplication(Application $application = null): void
+ {
+ if ($this->command instanceof parent) {
+ $this->command->setApplication($application);
+ }
+
+ parent::setApplication($application);
+ }
+
+ public function setHelperSet(HelperSet $helperSet): void
+ {
+ if ($this->command instanceof parent) {
+ $this->command->setHelperSet($helperSet);
+ }
+
+ parent::setHelperSet($helperSet);
+ }
+
+ public function isEnabled(): bool
+ {
+ return $this->isEnabled ?? $this->getCommand()->isEnabled();
+ }
+
+ public function run(InputInterface $input, OutputInterface $output): int
+ {
+ return $this->getCommand()->run($input, $output);
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ $this->getCommand()->complete($input, $suggestions);
+ }
+
+ /**
+ * @return $this
+ */
+ public function setCode(callable $code): self
+ {
+ $this->getCommand()->setCode($code);
+
+ return $this;
+ }
+
+ /**
+ * @internal
+ */
+ public function mergeApplicationDefinition(bool $mergeArgs = true): void
+ {
+ $this->getCommand()->mergeApplicationDefinition($mergeArgs);
+ }
+
+ /**
+ * @return $this
+ */
+ public function setDefinition($definition): self
+ {
+ $this->getCommand()->setDefinition($definition);
+
+ return $this;
+ }
+
+ public function getDefinition(): InputDefinition
+ {
+ return $this->getCommand()->getDefinition();
+ }
+
+ public function getNativeDefinition(): InputDefinition
+ {
+ return $this->getCommand()->getNativeDefinition();
+ }
+
+ /**
+ * @return $this
+ */
+ public function addArgument(string $name, int $mode = null, string $description = '', $default = null): self
+ {
+ $this->getCommand()->addArgument($name, $mode, $description, $default);
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null): self
+ {
+ $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default);
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setProcessTitle(string $title): self
+ {
+ $this->getCommand()->setProcessTitle($title);
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHelp(string $help): self
+ {
+ $this->getCommand()->setHelp($help);
+
+ return $this;
+ }
+
+ public function getHelp(): string
+ {
+ return $this->getCommand()->getHelp();
+ }
+
+ public function getProcessedHelp(): string
+ {
+ return $this->getCommand()->getProcessedHelp();
+ }
+
+ public function getSynopsis(bool $short = false): string
+ {
+ return $this->getCommand()->getSynopsis($short);
+ }
+
+ /**
+ * @return $this
+ */
+ public function addUsage(string $usage): self
+ {
+ $this->getCommand()->addUsage($usage);
+
+ return $this;
+ }
+
+ public function getUsages(): array
+ {
+ return $this->getCommand()->getUsages();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getHelper(string $name)
+ {
+ return $this->getCommand()->getHelper($name);
+ }
+
+ public function getCommand(): parent
+ {
+ if (!$this->command instanceof \Closure) {
+ return $this->command;
+ }
+
+ $command = $this->command = ($this->command)();
+ $command->setApplication($this->getApplication());
+
+ if (null !== $this->getHelperSet()) {
+ $command->setHelperSet($this->getHelperSet());
+ }
+
+ $command->setName($this->getName())
+ ->setAliases($this->getAliases())
+ ->setHidden($this->isHidden())
+ ->setDescription($this->getDescription());
+
+ // Will throw if the command is not correctly initialized.
+ $command->getDefinition();
+
+ return $command;
+ }
+}
diff --git a/lib/symfony/console/Command/ListCommand.php b/lib/symfony/console/Command/ListCommand.php
index 7259b1263..f04a4ef67 100644
--- a/lib/symfony/console/Command/ListCommand.php
+++ b/lib/symfony/console/Command/ListCommand.php
@@ -11,9 +11,11 @@
namespace Symfony\Component\Console\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,37 +34,34 @@ class ListCommand extends Command
{
$this
->setName('list')
- ->setDefinition($this->createDefinition())
- ->setDescription('Lists commands')
+ ->setDefinition([
+ new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
+ new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
+ new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
+ new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
+ ])
+ ->setDescription('List commands')
->setHelp(<<<'EOF'
The
- */
-class BufferingLogger extends AbstractLogger
-{
- private $logs = [];
-
- public function log($level, $message, array $context = [])
- {
- $this->logs[] = [$level, $message, $context];
- }
-
- public function cleanLogs()
- {
- $logs = $this->logs;
- $this->logs = [];
-
- return $logs;
- }
-}
diff --git a/lib/symfony/debug/CHANGELOG.md b/lib/symfony/debug/CHANGELOG.md
deleted file mode 100644
index 31c67eb60..000000000
--- a/lib/symfony/debug/CHANGELOG.md
+++ /dev/null
@@ -1,64 +0,0 @@
-CHANGELOG
-=========
-
-3.4.0
------
-
-* deprecated `ErrorHandler::stackErrors()` and `ErrorHandler::unstackErrors()`
-
-3.3.0
------
-
-* deprecated the `ContextErrorException` class: use \ErrorException directly now
-
-3.2.0
------
-
-* `FlattenException::getTrace()` now returns additional type descriptions
- `integer` and `float`.
-
-
-3.0.0
------
-
-* removed classes, methods and interfaces deprecated in 2.x
-
-2.8.0
------
-
-* added BufferingLogger for errors that happen before a proper logger is configured
-* allow throwing from `__toString()` with `return trigger_error($e, E_USER_ERROR);`
-* deprecate ExceptionHandler::createResponse
-
-2.7.0
------
-
-* added deprecations checking for parent interfaces/classes to DebugClassLoader
-* added ZTS support to symfony_debug extension
-* added symfony_debug_backtrace() to symfony_debug extension
- to track the backtrace of fatal errors
-
-2.6.0
------
-
-* generalized ErrorHandler and ExceptionHandler,
- with some new methods and others deprecated
-* enhanced error messages for uncaught exceptions
-
-2.5.0
------
-
-* added ExceptionHandler::setHandler()
-* added UndefinedMethodFatalErrorHandler
-* deprecated DummyException
-
-2.4.0
------
-
- * added a DebugClassLoader able to wrap any autoloader providing a findFile method
- * improved error messages for not found classes and functions
-
-2.3.0
------
-
- * added the component
diff --git a/lib/symfony/debug/Debug.php b/lib/symfony/debug/Debug.php
deleted file mode 100644
index 746f3290c..000000000
--- a/lib/symfony/debug/Debug.php
+++ /dev/null
@@ -1,60 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Debug;
-
-/**
- * Registers all the debug tools.
- *
- * @author Fabien Potencier
- */
-class DebugClassLoader
-{
- private $classLoader;
- private $isFinder;
- private $loaded = [];
- private static $caseCheck;
- private static $checkedClasses = [];
- private static $final = [];
- private static $finalMethods = [];
- private static $deprecated = [];
- private static $internal = [];
- private static $internalMethods = [];
- private static $php7Reserved = ['int' => 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1];
- private static $darwinCache = ['/' => ['/', []]];
-
- public function __construct(callable $classLoader)
- {
- $this->classLoader = $classLoader;
- $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');
-
- if (!isset(self::$caseCheck)) {
- $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
- $i = strrpos($file, \DIRECTORY_SEPARATOR);
- $dir = substr($file, 0, 1 + $i);
- $file = substr($file, 1 + $i);
- $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
- $test = realpath($dir.$test);
-
- if (false === $test || false === $i) {
- // filesystem is case sensitive
- self::$caseCheck = 0;
- } elseif (substr($test, -\strlen($file)) === $file) {
- // filesystem is case insensitive and realpath() normalizes the case of characters
- self::$caseCheck = 1;
- } elseif (false !== stripos(\PHP_OS, 'darwin')) {
- // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
- self::$caseCheck = 2;
- } else {
- // filesystem case checks failed, fallback to disabling them
- self::$caseCheck = 0;
- }
- }
- }
-
- /**
- * Gets the wrapped class loader.
- *
- * @return callable The wrapped class loader
- */
- public function getClassLoader()
- {
- return $this->classLoader;
- }
-
- /**
- * Wraps all autoloaders.
- */
- public static function enable()
- {
- // Ensures we don't hit https://bugs.php.net/42098
- class_exists('Symfony\Component\Debug\ErrorHandler');
- class_exists('Psr\Log\LogLevel');
-
- if (!\is_array($functions = spl_autoload_functions())) {
- return;
- }
-
- foreach ($functions as $function) {
- spl_autoload_unregister($function);
- }
-
- foreach ($functions as $function) {
- if (!\is_array($function) || !$function[0] instanceof self) {
- $function = [new static($function), 'loadClass'];
- }
-
- spl_autoload_register($function);
- }
- }
-
- /**
- * Disables the wrapping.
- */
- public static function disable()
- {
- if (!\is_array($functions = spl_autoload_functions())) {
- return;
- }
-
- foreach ($functions as $function) {
- spl_autoload_unregister($function);
- }
-
- foreach ($functions as $function) {
- if (\is_array($function) && $function[0] instanceof self) {
- $function = $function[0]->getClassLoader();
- }
-
- spl_autoload_register($function);
- }
- }
-
- /**
- * @return string|null
- */
- public function findFile($class)
- {
- return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null;
- }
-
- /**
- * Loads the given class or interface.
- *
- * @param string $class The name of the class
- *
- * @throws \RuntimeException
- */
- public function loadClass($class)
- {
- $e = error_reporting(error_reporting() | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR);
-
- try {
- if ($this->isFinder && !isset($this->loaded[$class])) {
- $this->loaded[$class] = true;
- if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
- // no-op
- } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
- include $file;
-
- return;
- } elseif (false === include $file) {
- return;
- }
- } else {
- \call_user_func($this->classLoader, $class);
- $file = false;
- }
- } finally {
- error_reporting($e);
- }
-
- $this->checkClass($class, $file);
- }
-
- private function checkClass($class, $file = null)
- {
- $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
-
- if (null !== $file && $class && '\\' === $class[0]) {
- $class = substr($class, 1);
- }
-
- if ($exists) {
- if (isset(self::$checkedClasses[$class])) {
- return;
- }
- self::$checkedClasses[$class] = true;
-
- $refl = new \ReflectionClass($class);
- if (null === $file && $refl->isInternal()) {
- return;
- }
- $name = $refl->getName();
-
- if ($name !== $class && 0 === strcasecmp($name, $class)) {
- throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
- }
-
- $deprecations = $this->checkAnnotations($refl, $name);
-
- if (isset(self::$php7Reserved[strtolower($refl->getShortName())])) {
- $deprecations[] = sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName());
- }
-
- foreach ($deprecations as $message) {
- @trigger_error($message, \E_USER_DEPRECATED);
- }
- }
-
- if (!$file) {
- return;
- }
-
- if (!$exists) {
- if (false !== strpos($class, '/')) {
- throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class));
- }
-
- throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file));
- }
-
- if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) {
- throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
- }
- }
-
- public function checkAnnotations(\ReflectionClass $refl, $class)
- {
- $deprecations = [];
-
- // Don't trigger deprecations for classes in the same vendor
- if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) {
- $len = 0;
- $ns = '';
- } else {
- $ns = str_replace('_', '\\', substr($class, 0, $len));
- }
-
- // Detect annotations on the class
- if (false !== $doc = $refl->getDocComment()) {
- foreach (['final', 'deprecated', 'internal'] as $annotation) {
- if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
- self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
- }
- }
- }
-
- $parent = get_parent_class($class);
- $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
- if ($parent) {
- $parentAndOwnInterfaces[$parent] = $parent;
-
- if (!isset(self::$checkedClasses[$parent])) {
- $this->checkClass($parent);
- }
-
- if (isset(self::$final[$parent])) {
- $deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class);
- }
- }
-
- // Detect if the parent is annotated
- foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
- if (!isset(self::$checkedClasses[$use])) {
- $this->checkClass($use);
- }
- if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) {
- $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
- $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
-
- $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
- }
- if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) {
- $deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class);
- }
- }
-
- if (trait_exists($class)) {
- return $deprecations;
- }
-
- // Inherit @final and @internal annotations for methods
- self::$finalMethods[$class] = [];
- self::$internalMethods[$class] = [];
- foreach ($parentAndOwnInterfaces as $use) {
- foreach (['finalMethods', 'internalMethods'] as $property) {
- if (isset(self::${$property}[$use])) {
- self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
- }
- }
- }
-
- foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
- if ($method->class !== $class) {
- continue;
- }
-
- if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
- list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
- $deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
- }
-
- if (isset(self::$internalMethods[$class][$method->name])) {
- list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
- if (strncmp($ns, $declaringClass, $len)) {
- $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class);
- }
- }
-
- // Detect method annotations
- if (false === $doc = $method->getDocComment()) {
- continue;
- }
-
- foreach (['final', 'internal'] as $annotation) {
- if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) {
- $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
- self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
- }
- }
- }
-
- return $deprecations;
- }
-
- /**
- * @param string $file
- * @param string $class
- *
- * @return array|null
- */
- public function checkCase(\ReflectionClass $refl, $file, $class)
- {
- $real = explode('\\', $class.strrchr($file, '.'));
- $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
-
- $i = \count($tail) - 1;
- $j = \count($real) - 1;
-
- while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
- --$i;
- --$j;
- }
-
- array_splice($tail, 0, $i + 1);
-
- if (!$tail) {
- return null;
- }
-
- $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
- $tailLen = \strlen($tail);
- $real = $refl->getFileName();
-
- if (2 === self::$caseCheck) {
- $real = $this->darwinRealpath($real);
- }
-
- if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
- && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
- ) {
- return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)];
- }
-
- return null;
- }
-
- /**
- * `realpath` on MacOSX doesn't normalize the case of characters.
- */
- private function darwinRealpath($real)
- {
- $i = 1 + strrpos($real, '/');
- $file = substr($real, $i);
- $real = substr($real, 0, $i);
-
- if (isset(self::$darwinCache[$real])) {
- $kDir = $real;
- } else {
- $kDir = strtolower($real);
-
- if (isset(self::$darwinCache[$kDir])) {
- $real = self::$darwinCache[$kDir][0];
- } else {
- $dir = getcwd();
- chdir($real);
- $real = getcwd().'/';
- chdir($dir);
-
- $dir = $real;
- $k = $kDir;
- $i = \strlen($dir) - 1;
- while (!isset(self::$darwinCache[$k])) {
- self::$darwinCache[$k] = [$dir, []];
- self::$darwinCache[$dir] = &self::$darwinCache[$k];
-
- while ('/' !== $dir[--$i]) {
- }
- $k = substr($k, 0, ++$i);
- $dir = substr($dir, 0, $i--);
- }
- }
- }
-
- $dirFiles = self::$darwinCache[$kDir][1];
-
- if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) {
- // Get the file name from "file_name.php(123) : eval()'d code"
- $file = substr($file, 0, strrpos($file, '(', -17));
- }
-
- if (isset($dirFiles[$file])) {
- return $real.$dirFiles[$file];
- }
-
- $kFile = strtolower($file);
-
- if (!isset($dirFiles[$kFile])) {
- foreach (scandir($real, 2) as $f) {
- if ('.' !== $f[0]) {
- $dirFiles[$f] = $f;
- if ($f === $file) {
- $kFile = $k = $file;
- } elseif ($f !== $k = strtolower($f)) {
- $dirFiles[$k] = $f;
- }
- }
- }
- self::$darwinCache[$kDir][1] = $dirFiles;
- }
-
- return $real.$dirFiles[$kFile];
- }
-
- /**
- * `class_implements` includes interfaces from the parents so we have to manually exclude them.
- *
- * @param string $class
- * @param string|false $parent
- *
- * @return string[]
- */
- private function getOwnInterfaces($class, $parent)
- {
- $ownInterfaces = class_implements($class, false);
-
- if ($parent) {
- foreach (class_implements($parent, false) as $interface) {
- unset($ownInterfaces[$interface]);
- }
- }
-
- foreach ($ownInterfaces as $interface) {
- foreach (class_implements($interface) as $interface) {
- unset($ownInterfaces[$interface]);
- }
- }
-
- return $ownInterfaces;
- }
-}
diff --git a/lib/symfony/debug/Exception/ClassNotFoundException.php b/lib/symfony/debug/Exception/ClassNotFoundException.php
deleted file mode 100644
index de5c45644..000000000
--- a/lib/symfony/debug/Exception/ClassNotFoundException.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Debug\Exception;
-
-/**
- * Class (or Trait or Interface) Not Found Exception.
- *
- * @author Konstanton Myakshin
- */
-class FatalThrowableError extends FatalErrorException
-{
- public function __construct(\Throwable $e)
- {
- if ($e instanceof \ParseError) {
- $message = 'Parse error: '.$e->getMessage();
- $severity = \E_PARSE;
- } elseif ($e instanceof \TypeError) {
- $message = 'Type error: '.$e->getMessage();
- $severity = \E_RECOVERABLE_ERROR;
- } else {
- $message = $e->getMessage();
- $severity = \E_ERROR;
- }
-
- \ErrorException::__construct(
- $message,
- $e->getCode(),
- $severity,
- $e->getFile(),
- $e->getLine(),
- $e->getPrevious()
- );
-
- $this->setTrace($e->getTrace());
- }
-}
diff --git a/lib/symfony/debug/Exception/FlattenException.php b/lib/symfony/debug/Exception/FlattenException.php
deleted file mode 100644
index 9514bdc5e..000000000
--- a/lib/symfony/debug/Exception/FlattenException.php
+++ /dev/null
@@ -1,263 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Debug\Exception;
-
-use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
-use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
-
-/**
- * FlattenException wraps a PHP Exception to be able to serialize it.
- *
- * Basically, this class removes all objects from the trace.
- *
- * @author Fabien Potencier
- */
-class ExceptionHandler
-{
- private $debug;
- private $charset;
- private $handler;
- private $caughtBuffer;
- private $caughtLength;
- private $fileLinkFormat;
-
- public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
- {
- $this->debug = $debug;
- $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
- $this->fileLinkFormat = $fileLinkFormat;
- }
-
- /**
- * Registers the exception handler.
- *
- * @param bool $debug Enable/disable debug mode, where the stack trace is displayed
- * @param string|null $charset The charset used by exception messages
- * @param string|null $fileLinkFormat The IDE link template
- *
- * @return static
- */
- public static function register($debug = true, $charset = null, $fileLinkFormat = null)
- {
- $handler = new static($debug, $charset, $fileLinkFormat);
-
- $prev = set_exception_handler([$handler, 'handle']);
- if (\is_array($prev) && $prev[0] instanceof ErrorHandler) {
- restore_exception_handler();
- $prev[0]->setExceptionHandler([$handler, 'handle']);
- }
-
- return $handler;
- }
-
- /**
- * Sets a user exception handler.
- *
- * @param callable $handler An handler that will be called on Exception
- *
- * @return callable|null The previous exception handler if any
- */
- public function setHandler(callable $handler = null)
- {
- $old = $this->handler;
- $this->handler = $handler;
-
- return $old;
- }
-
- /**
- * Sets the format for links to source files.
- *
- * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
- *
- * @return string The previous file link format
- */
- public function setFileLinkFormat($fileLinkFormat)
- {
- $old = $this->fileLinkFormat;
- $this->fileLinkFormat = $fileLinkFormat;
-
- return $old;
- }
-
- /**
- * Sends a response for the given Exception.
- *
- * To be as fail-safe as possible, the exception is first handled
- * by our simple exception handler, then by the user exception handler.
- * The latter takes precedence and any output from the former is cancelled,
- * if and only if nothing bad happens in this handling path.
- */
- public function handle(\Exception $exception)
- {
- if (null === $this->handler || $exception instanceof OutOfMemoryException) {
- $this->sendPhpResponse($exception);
-
- return;
- }
-
- $caughtLength = $this->caughtLength = 0;
-
- ob_start(function ($buffer) {
- $this->caughtBuffer = $buffer;
-
- return '';
- });
-
- $this->sendPhpResponse($exception);
- while (null === $this->caughtBuffer && ob_end_flush()) {
- // Empty loop, everything is in the condition
- }
- if (isset($this->caughtBuffer[0])) {
- ob_start(function ($buffer) {
- if ($this->caughtLength) {
- // use substr_replace() instead of substr() for mbstring overloading resistance
- $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength);
- if (isset($cleanBuffer[0])) {
- $buffer = $cleanBuffer;
- }
- }
-
- return $buffer;
- });
-
- echo $this->caughtBuffer;
- $caughtLength = ob_get_length();
- }
- $this->caughtBuffer = null;
-
- try {
- \call_user_func($this->handler, $exception);
- $this->caughtLength = $caughtLength;
- } catch (\Exception $e) {
- if (!$caughtLength) {
- // All handlers failed. Let PHP handle that now.
- throw $exception;
- }
- }
- }
-
- /**
- * Sends the error associated with the given Exception as a plain PHP response.
- *
- * This method uses plain PHP functions like header() and echo to output
- * the response.
- *
- * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
- */
- public function sendPhpResponse($exception)
- {
- if (!$exception instanceof FlattenException) {
- $exception = FlattenException::create($exception);
- }
-
- if (!headers_sent()) {
- header(sprintf('HTTP/1.0 %s', $exception->getStatusCode()));
- foreach ($exception->getHeaders() as $name => $value) {
- header($name.': '.$value, false);
- }
- header('Content-Type: text/html; charset='.$this->charset);
- }
-
- echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
- }
-
- /**
- * Gets the full HTML content associated with the given exception.
- *
- * @param \Exception|FlattenException $exception An \Exception or FlattenException instance
- *
- * @return string The HTML content as a string
- */
- public function getHtml($exception)
- {
- if (!$exception instanceof FlattenException) {
- $exception = FlattenException::create($exception);
- }
-
- return $this->decorate($this->getContent($exception), $this->getStylesheet($exception));
- }
-
- /**
- * Gets the HTML content associated with the given exception.
- *
- * @return string The content as a string
- */
- public function getContent(FlattenException $exception)
- {
- switch ($exception->getStatusCode()) {
- case 404:
- $title = 'Sorry, the page you are looking for could not be found.';
- break;
- default:
- $title = 'Whoops, looks like something went wrong.';
- }
-
- if (!$this->debug) {
- return << %s
+ */
+trait ReferenceSetArgumentTrait
+{
+ private $values;
+
+ /**
+ * @param Reference[] $values
+ */
+ public function __construct(array $values)
+ {
+ $this->setValues($values);
+ }
+
+ /**
+ * @return Reference[]
+ */
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ /**
+ * @param Reference[] $values The service references to put in the set
+ */
+ public function setValues(array $values)
+ {
+ foreach ($values as $k => $v) {
+ if (null !== $v && !$v instanceof Reference) {
+ throw new InvalidArgumentException(sprintf('A "%s" must hold only Reference instances, "%s" given.', __CLASS__, get_debug_type($v)));
+ }
+ }
+
+ $this->values = $values;
+ }
+}
diff --git a/lib/symfony/dependency-injection/Argument/RewindableGenerator.php b/lib/symfony/dependency-injection/Argument/RewindableGenerator.php
index b00a36c34..41fec786f 100644
--- a/lib/symfony/dependency-injection/Argument/RewindableGenerator.php
+++ b/lib/symfony/dependency-injection/Argument/RewindableGenerator.php
@@ -28,14 +28,14 @@ class RewindableGenerator implements \IteratorAggregate, \Countable
$this->count = $count;
}
- public function getIterator()
+ public function getIterator(): \Traversable
{
$g = $this->generator;
return $g();
}
- public function count()
+ public function count(): int
{
if (\is_callable($count = $this->count)) {
$this->count = $count();
diff --git a/lib/symfony/dependency-injection/Argument/ServiceLocator.php b/lib/symfony/dependency-injection/Argument/ServiceLocator.php
new file mode 100644
index 000000000..bc138fe23
--- /dev/null
+++ b/lib/symfony/dependency-injection/Argument/ServiceLocator.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Argument;
+
+use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class ServiceLocator extends BaseServiceLocator
+{
+ private $factory;
+ private $serviceMap;
+ private $serviceTypes;
+
+ public function __construct(\Closure $factory, array $serviceMap, array $serviceTypes = null)
+ {
+ $this->factory = $factory;
+ $this->serviceMap = $serviceMap;
+ $this->serviceTypes = $serviceTypes;
+ parent::__construct($serviceMap);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ public function get(string $id)
+ {
+ return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProvidedServices(): array
+ {
+ return $this->serviceTypes ?? $this->serviceTypes = array_map(function () { return '?'; }, $this->serviceMap);
+ }
+}
diff --git a/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php b/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php
new file mode 100644
index 000000000..fcbf478c6
--- /dev/null
+++ b/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Argument;
+
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Represents a closure acting as a service locator.
+ *
+ * @author Nicolas Grekas
+ */
+class ServiceLocatorArgument implements ArgumentInterface
+{
+ use ReferenceSetArgumentTrait;
+
+ private $taggedIteratorArgument;
+
+ /**
+ * @param Reference[]|TaggedIteratorArgument $values
+ */
+ public function __construct($values = [])
+ {
+ if ($values instanceof TaggedIteratorArgument) {
+ $this->taggedIteratorArgument = $values;
+ $this->values = [];
+ } else {
+ $this->setValues($values);
+ }
+ }
+
+ public function getTaggedIteratorArgument(): ?TaggedIteratorArgument
+ {
+ return $this->taggedIteratorArgument;
+ }
+}
diff --git a/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php b/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php
index f00e53391..1ba8de790 100644
--- a/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php
+++ b/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php
@@ -19,19 +19,55 @@ namespace Symfony\Component\DependencyInjection\Argument;
class TaggedIteratorArgument extends IteratorArgument
{
private $tag;
+ private $indexAttribute;
+ private $defaultIndexMethod;
+ private $defaultPriorityMethod;
+ private $needsIndexes = false;
/**
- * @param string $tag
+ * @param string $tag The name of the tag identifying the target services
+ * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
+ * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
+ * @param bool $needsIndexes Whether indexes are required and should be generated when computing the map
+ * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute
*/
- public function __construct($tag)
+ public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null)
{
parent::__construct([]);
- $this->tag = (string) $tag;
+ if (null === $indexAttribute && $needsIndexes) {
+ $indexAttribute = preg_match('/[^.]++$/', $tag, $m) ? $m[0] : $tag;
+ }
+
+ $this->tag = $tag;
+ $this->indexAttribute = $indexAttribute;
+ $this->defaultIndexMethod = $defaultIndexMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name' : null);
+ $this->needsIndexes = $needsIndexes;
+ $this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null);
}
public function getTag()
{
return $this->tag;
}
+
+ public function getIndexAttribute(): ?string
+ {
+ return $this->indexAttribute;
+ }
+
+ public function getDefaultIndexMethod(): ?string
+ {
+ return $this->defaultIndexMethod;
+ }
+
+ public function needsIndexes(): bool
+ {
+ return $this->needsIndexes;
+ }
+
+ public function getDefaultPriorityMethod(): ?string
+ {
+ return $this->defaultPriorityMethod;
+ }
}
diff --git a/lib/symfony/dependency-injection/Attribute/AsTaggedItem.php b/lib/symfony/dependency-injection/Attribute/AsTaggedItem.php
new file mode 100644
index 000000000..232033633
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/AsTaggedItem.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+/**
+ * An attribute to tell under which index and priority a service class should be found in tagged iterators/locators.
+ *
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class AsTaggedItem
+{
+ public function __construct(
+ public ?string $index = null,
+ public ?int $priority = null,
+ ) {
+ }
+}
diff --git a/lib/symfony/dependency-injection/Attribute/Autoconfigure.php b/lib/symfony/dependency-injection/Attribute/Autoconfigure.php
new file mode 100644
index 000000000..abab04010
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/Autoconfigure.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+/**
+ * An attribute to tell how a base type should be autoconfigured.
+ *
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
+class Autoconfigure
+{
+ public function __construct(
+ public ?array $tags = null,
+ public ?array $calls = null,
+ public ?array $bind = null,
+ public bool|string|null $lazy = null,
+ public ?bool $public = null,
+ public ?bool $shared = null,
+ public ?bool $autowire = null,
+ public ?array $properties = null,
+ public array|string|null $configurator = null,
+ ) {
+ }
+}
diff --git a/lib/symfony/dependency-injection/Attribute/AutoconfigureTag.php b/lib/symfony/dependency-injection/Attribute/AutoconfigureTag.php
new file mode 100644
index 000000000..ed5807ca0
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/AutoconfigureTag.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+/**
+ * An attribute to tell how a base type should be tagged.
+ *
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
+class AutoconfigureTag extends Autoconfigure
+{
+ public function __construct(string $name = null, array $attributes = [])
+ {
+ parent::__construct(
+ tags: [
+ [$name ?? 0 => $attributes],
+ ]
+ );
+ }
+}
diff --git a/lib/symfony/dependency-injection/Attribute/TaggedIterator.php b/lib/symfony/dependency-injection/Attribute/TaggedIterator.php
new file mode 100644
index 000000000..d498f4647
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/TaggedIterator.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+#[\Attribute(\Attribute::TARGET_PARAMETER)]
+class TaggedIterator
+{
+ public function __construct(
+ public string $tag,
+ public ?string $indexAttribute = null,
+ public ?string $defaultIndexMethod = null,
+ public ?string $defaultPriorityMethod = null,
+ ) {
+ }
+}
diff --git a/lib/symfony/dependency-injection/Attribute/TaggedLocator.php b/lib/symfony/dependency-injection/Attribute/TaggedLocator.php
new file mode 100644
index 000000000..4617e0f51
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/TaggedLocator.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+#[\Attribute(\Attribute::TARGET_PARAMETER)]
+class TaggedLocator
+{
+ public function __construct(
+ public string $tag,
+ public ?string $indexAttribute = null,
+ public ?string $defaultIndexMethod = null,
+ public ?string $defaultPriorityMethod = null,
+ ) {
+ }
+}
diff --git a/lib/symfony/dependency-injection/Attribute/Target.php b/lib/symfony/dependency-injection/Attribute/Target.php
new file mode 100644
index 000000000..a7a4d8b5f
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/Target.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+
+/**
+ * An attribute to tell how a dependency is used and hint named autowiring aliases.
+ *
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_PARAMETER)]
+final class Target
+{
+ /**
+ * @var string
+ */
+ public $name;
+
+ public function __construct(string $name)
+ {
+ $this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name))));
+ }
+
+ public static function parseName(\ReflectionParameter $parameter): string
+ {
+ if (80000 > \PHP_VERSION_ID || !$target = $parameter->getAttributes(self::class)[0] ?? null) {
+ return $parameter->name;
+ }
+
+ $name = $target->newInstance()->name;
+
+ if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) {
+ if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) {
+ $function = $function->class.'::'.$function->name;
+ } else {
+ $function = $function->name;
+ }
+
+ throw new InvalidArgumentException(sprintf('Invalid #[Target] name "%s" on parameter "$%s" of "%s()": the first character must be a letter.', $name, $parameter->name, $function));
+ }
+
+ return $name;
+ }
+}
diff --git a/lib/symfony/dependency-injection/Attribute/When.php b/lib/symfony/dependency-injection/Attribute/When.php
new file mode 100644
index 000000000..60b7af04b
--- /dev/null
+++ b/lib/symfony/dependency-injection/Attribute/When.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Attribute;
+
+/**
+ * An attribute to tell under which environement this class should be registered as a service.
+ *
+ * @author Nicolas Grekas
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION | \Attribute::IS_REPEATABLE)]
+class When
+{
+ public function __construct(
+ public string $env,
+ ) {
+ }
+}
diff --git a/lib/symfony/dependency-injection/CHANGELOG.md b/lib/symfony/dependency-injection/CHANGELOG.md
index a004161b9..88a6df8df 100644
--- a/lib/symfony/dependency-injection/CHANGELOG.md
+++ b/lib/symfony/dependency-injection/CHANGELOG.md
@@ -1,6 +1,193 @@
CHANGELOG
=========
+5.4
+---
+ * Add `$defaultIndexMethod` and `$defaultPriorityMethod` to `TaggedIterator` and `TaggedLocator` attributes
+ * Add `service_closure()` to the PHP-DSL
+ * Add support for autoconfigurable attributes on methods, properties and parameters
+ * Make auto-aliases private by default
+ * Add support for autowiring union and intersection types
+
+5.3
+---
+
+ * Add `ServicesConfigurator::remove()` in the PHP-DSL
+ * Add `%env(not:...)%` processor to negate boolean values
+ * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
+ * Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators
+ * Add autoconfigurable attributes
+ * Add support for autowiring tagged iterators and locators via attributes on PHP 8
+ * Add support for per-env configuration in XML and Yaml loaders
+ * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
+ * Add support an integer return value for default_index_method
+ * Add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match
+ * Add `env()` and `EnvConfigurator` in the PHP-DSL
+ * Add support for `ConfigBuilder` in the `PhpFileLoader`
+ * Add `ContainerConfigurator::env()` to get the current environment
+ * Add `#[Target]` to tell how a dependency is used and hint named autowiring aliases
+
+5.2.0
+-----
+
+ * added `param()` and `abstract_arg()` in the PHP-DSL
+ * deprecated `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead
+ * added support for the `#[Required]` attribute
+
+5.1.0
+-----
+
+ * deprecated `inline()` in favor of `inline_service()` and `ref()` in favor of `service()` when using the PHP-DSL
+ * allow decorators to reference their decorated service using the special `.inner` id
+ * added support to autowire public typed properties in php 7.4
+ * added support for defining method calls, a configurator, and property setters in `InlineServiceConfigurator`
+ * added possibility to define abstract service arguments
+ * allowed mixing "parent" and instanceof-conditionals/defaults/bindings
+ * updated the signature of method `Definition::setDeprecated()` to `Definition::setDeprecation(string $package, string $version, string $message)`
+ * updated the signature of method `Alias::setDeprecated()` to `Alias::setDeprecation(string $package, string $version, string $message)`
+ * updated the signature of method `DeprecateTrait::deprecate()` to `DeprecateTrait::deprecation(string $package, string $version, string $message)`
+ * deprecated the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service,
+ configure them explicitly instead
+ * added class `Symfony\Component\DependencyInjection\Dumper\Preloader` to help with preloading on PHP 7.4+
+ * added tags `container.preload`/`.no_preload` to declare extra classes to preload/services to not preload
+ * allowed loading and dumping tags with an attribute named "name"
+ * deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead
+ * deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead
+ * added support of PHP8 static return type for withers
+ * added `AliasDeprecatedPublicServicesPass` to deprecate public services to private
+
+5.0.0
+-----
+
+ * removed support for auto-discovered extension configuration class which does not implement `ConfigurationInterface`
+ * removed support for non-string default env() parameters
+ * moved `ServiceSubscriberInterface` to the `Symfony\Contracts\Service` namespace
+ * removed `RepeatedPass` and `RepeatablePassInterface`
+ * removed support for short factory/configurator syntax from `YamlFileLoader`
+ * removed `ResettableContainerInterface`, use `ResetInterface` instead
+ * added argument `$returnsClone` to `Definition::addMethodCall()`
+ * removed `tagged`, use `tagged_iterator` instead
+
+4.4.0
+-----
+
+ * added `CheckTypeDeclarationsPass` to check injected parameters type during compilation
+ * added support for opcache.preload by generating a preloading script in the cache folder
+ * added support for dumping the container in one file instead of many files
+ * deprecated support for short factories and short configurators in Yaml
+ * added `tagged_iterator` alias for `tagged` which might be deprecated in a future version
+ * deprecated passing an instance of `Symfony\Component\DependencyInjection\Parameter` as class name to `Symfony\Component\DependencyInjection\Definition`
+ * added support for binding iterable and tagged services
+ * made singly-implemented interfaces detection be scoped by file
+ * added ability to define a static priority method for tagged service
+ * added support for improved syntax to define method calls in Yaml
+ * made the `%env(base64:...)%` processor able to decode base64url
+ * added ability to choose behavior of decorations on non existent decorated services
+
+4.3.0
+-----
+
+ * added `%env(trim:...)%` processor to trim a string value
+ * added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%`
+ * added `%env(url:...)%` processor to convert an URL or DNS into an array of components
+ * added `%env(query_string:...)%` processor to convert a query string into an array of key values
+ * added support for deprecating aliases
+ * made `ContainerParametersResource` final and not implement `Serializable` anymore
+ * added `ReverseContainer`: a container that turns services back to their ids
+ * added ability to define an index for a tagged collection
+ * added ability to define an index for services in an injected service locator argument
+ * made `ServiceLocator` implement `ServiceProviderInterface`
+ * deprecated support for non-string default env() parameters
+ * added `%env(require:...)%` processor to `require()` a PHP file and use the value returned from it
+
+4.2.0
+-----
+
+ * added `ContainerBuilder::registerAliasForArgument()` to support autowiring by type+name
+ * added support for binding by type+name
+ * added `ServiceSubscriberTrait` to ease implementing `ServiceSubscriberInterface` using methods' return types
+ * added `ServiceLocatorArgument` and `!service_locator` config tag for creating optimized service-locators
+ * added support for autoconfiguring bindings
+ * added `%env(key:...)%` processor to fetch a specific key from an array
+ * deprecated `ServiceSubscriberInterface`, use the same interface from the `Symfony\Contracts\Service` namespace instead
+ * deprecated `ResettableContainerInterface`, use `Symfony\Contracts\Service\ResetInterface` instead
+
+4.1.0
+-----
+
+ * added support for variadics in named arguments
+ * added PSR-11 `ContainerBagInterface` and its `ContainerBag` implementation to access parameters as-a-service
+ * added support for service's decorators autowiring
+ * deprecated the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods
+ * environment variables are validated when used in extension configuration
+ * deprecated support for auto-discovered extension configuration class which does not implement `ConfigurationInterface`
+
+4.0.0
+-----
+
+ * Relying on service auto-registration while autowiring is not supported anymore.
+ Explicitly inject your dependencies or create services whose ids are
+ their fully-qualified class name.
+
+ Before:
+
+ ```php
+ namespace App\Controller;
+
+ use App\Mailer;
+
+ class DefaultController
+ {
+ public function __construct(Mailer $mailer) {
+ // ...
+ }
+
+ // ...
+ }
+ ```
+ ```yml
+ services:
+ App\Controller\DefaultController:
+ autowire: true
+ ```
+
+ After:
+
+ ```php
+ // same PHP code
+ ```
+ ```yml
+ services:
+ App\Controller\DefaultController:
+ autowire: true
+
+ # or
+ # App\Controller\DefaultController:
+ # arguments: { $mailer: "@App\Mailer" }
+
+ App\Mailer:
+ autowire: true
+ ```
+ * removed autowiring services based on the types they implement
+ * added a third `$methodName` argument to the `getProxyFactoryCode()` method
+ of the `DumperInterface`
+ * removed support for autowiring types
+ * removed `Container::isFrozen`
+ * removed support for dumping an ucompiled container in `PhpDumper`
+ * removed support for generating a dumped `Container` without populating the method map
+ * removed support for case insensitive service identifiers
+ * removed the `DefinitionDecorator` class, replaced by `ChildDefinition`
+ * removed the `AutowireServiceResource` class and related `AutowirePass::createResourceForClass()` method
+ * removed `LoggingFormatter`, `Compiler::getLoggingFormatter()` and `addLogMessage()` class and methods, use the `ContainerBuilder::log()` method instead
+ * removed `FactoryReturnTypePass`
+ * removed `ContainerBuilder::addClassResource()`, use the `addObjectResource()` or the `getReflectionClass()` method instead.
+ * removed support for top-level anonymous services
+ * removed silent behavior for unused attributes and elements
+ * removed support for setting and accessing private services in `Container`
+ * removed support for setting pre-defined services in `Container`
+ * removed support for case insensitivity of parameter names
+ * removed `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead
+
3.4.0
-----
@@ -16,7 +203,6 @@ CHANGELOG
* added `TaggedIteratorArgument` with YAML (`!tagged foo`) and XML (`
@@ -28,6 +32,10 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
protected $container;
protected $currentId;
+ private $processExpressions = false;
+ private $expressionLanguage;
+ private $inExpression = false;
+
/**
* {@inheritdoc}
*/
@@ -42,15 +50,29 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
}
+ protected function enableExpressionProcessing()
+ {
+ $this->processExpressions = true;
+ }
+
+ protected function inExpression(bool $reset = true): bool
+ {
+ $inExpression = $this->inExpression;
+ if ($reset) {
+ $this->inExpression = false;
+ }
+
+ return $inExpression;
+ }
+
/**
* Processes a value found in a definition tree.
*
* @param mixed $value
- * @param bool $isRoot
*
- * @return mixed The processed value
+ * @return mixed
*/
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
if (\is_array($value)) {
foreach ($value as $k => $v) {
@@ -63,6 +85,8 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
} elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues()));
+ } elseif ($value instanceof Expression && $this->processExpressions) {
+ $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']);
} elseif ($value instanceof Definition) {
$value->setArguments($this->processValue($value->getArguments()));
$value->setProperties($this->processValue($value->getProperties()));
@@ -81,13 +105,11 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
/**
- * @param bool $required
- *
* @return \ReflectionFunctionAbstract|null
*
* @throws RuntimeException
*/
- protected function getConstructor(Definition $definition, $required)
+ protected function getConstructor(Definition $definition, bool $required)
{
if ($definition->isSynthetic()) {
return null;
@@ -106,23 +128,36 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
if ($factory) {
- list($class, $method) = $factory;
- if ($class instanceof Reference) {
- $class = $this->container->findDefinition((string) $class)->getClass();
- } elseif (null === $class) {
- $class = $definition->getClass();
- }
+ [$class, $method] = $factory;
+
if ('__construct' === $method) {
throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
}
+ if ($class instanceof Reference) {
+ $factoryDefinition = $this->container->findDefinition((string) $class);
+ while ((null === $class = $factoryDefinition->getClass()) && $factoryDefinition instanceof ChildDefinition) {
+ $factoryDefinition = $this->container->findDefinition($factoryDefinition->getParent());
+ }
+ } elseif ($class instanceof Definition) {
+ $class = $class->getClass();
+ } elseif (null === $class) {
+ $class = $definition->getClass();
+ }
+
return $this->getReflectionMethod(new Definition($class), $method);
}
- $class = $definition->getClass();
+ while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) {
+ $definition = $this->container->findDefinition($definition->getParent());
+ }
try {
if (!$r = $this->container->getReflectionClass($class)) {
+ if (null === $class) {
+ throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
+ }
+
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
} catch (\ReflectionException $e) {
@@ -140,19 +175,21 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
/**
- * @param string $method
- *
* @throws RuntimeException
*
* @return \ReflectionFunctionAbstract
*/
- protected function getReflectionMethod(Definition $definition, $method)
+ protected function getReflectionMethod(Definition $definition, string $method)
{
if ('__construct' === $method) {
return $this->getConstructor($definition, true);
}
- if (!$class = $definition->getClass()) {
+ while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) {
+ $definition = $this->container->findDefinition($definition->getParent());
+ }
+
+ if (null === $class) {
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
}
@@ -161,6 +198,10 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
}
if (!$r->hasMethod($method)) {
+ if ($r->hasMethod('__call') && ($r = $r->getMethod('__call')) && $r->isPublic()) {
+ return new \ReflectionMethod(static function (...$arguments) {}, '__invoke');
+ }
+
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
@@ -171,4 +212,31 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
return $r;
}
+
+ private function getExpressionLanguage(): ExpressionLanguage
+ {
+ if (null === $this->expressionLanguage) {
+ if (!class_exists(ExpressionLanguage::class)) {
+ throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
+ }
+
+ $providers = $this->container->getExpressionLanguageProviders();
+ $this->expressionLanguage = new ExpressionLanguage(null, $providers, function (string $arg): string {
+ if ('""' === substr_replace($arg, '', 1, -1)) {
+ $id = stripcslashes(substr($arg, 1, -1));
+ $this->inExpression = true;
+ $arg = $this->processValue(new Reference($id));
+ $this->inExpression = false;
+ if (!$arg instanceof Reference) {
+ throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, "%s" returned for service("%s").', static::class, get_debug_type($arg), $id));
+ }
+ $arg = sprintf('"%s"', $arg);
+ }
+
+ return sprintf('$this->get(%s)', $arg);
+ });
+ }
+
+ return $this->expressionLanguage;
+ }
}
diff --git a/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php b/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php
new file mode 100644
index 000000000..8d3fefe75
--- /dev/null
+++ b/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass
+{
+ private $tagName;
+
+ private $aliases = [];
+
+ public function __construct(string $tagName = 'container.private')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->tagName = $tagName;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function processValue($value, bool $isRoot = false)
+ {
+ if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) {
+ return new Reference($this->aliases[$id], $value->getInvalidBehavior());
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
+ if (null === $package = $tags[0]['package'] ?? null) {
+ throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
+ }
+
+ if (null === $version = $tags[0]['version'] ?? null) {
+ throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
+ }
+
+ $definition = $container->getDefinition($id);
+ if (!$definition->isPublic() || $definition->isPrivate()) {
+ continue;
+ }
+
+ $container
+ ->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id)
+ ->setPublic(true)
+ ->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.');
+
+ $container->setDefinition($aliasId, $definition);
+
+ $this->aliases[$id] = $aliasId;
+ }
+
+ parent::process($container);
+ }
+}
diff --git a/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php b/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php
index bff9d4207..f7dbe6c8a 100644
--- a/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php
+++ b/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php
@@ -12,13 +12,11 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
-use Symfony\Component\DependencyInjection\Exception\RuntimeException;
-use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\ExpressionLanguage\Expression;
/**
* Run this pass before passes that need to know more about the relation of
@@ -28,32 +26,28 @@ use Symfony\Component\ExpressionLanguage\Expression;
* retrieve the graph in other passes from the compiler.
*
* @author Johannes M. Schmitt
*/
-class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements RepeatablePassInterface
+class AnalyzeServiceReferencesPass extends AbstractRecursivePass
{
private $graph;
private $currentDefinition;
private $onlyConstructorArguments;
private $hasProxyDumper;
private $lazy;
- private $expressionLanguage;
private $byConstructor;
+ private $byFactory;
+ private $definitions;
+ private $aliases;
/**
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
*/
- public function __construct($onlyConstructorArguments = false, $hasProxyDumper = true)
+ public function __construct(bool $onlyConstructorArguments = false, bool $hasProxyDumper = true)
{
- $this->onlyConstructorArguments = (bool) $onlyConstructorArguments;
- $this->hasProxyDumper = (bool) $hasProxyDumper;
- }
-
- /**
- * {@inheritdoc}
- */
- public function setRepeatedPass(RepeatedPass $repeatedPass)
- {
- // no-op for BC
+ $this->onlyConstructorArguments = $onlyConstructorArguments;
+ $this->hasProxyDumper = $hasProxyDumper;
+ $this->enableExpressionProcessing();
}
/**
@@ -66,34 +60,37 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
$this->graph->clear();
$this->lazy = false;
$this->byConstructor = false;
+ $this->byFactory = false;
+ $this->definitions = $container->getDefinitions();
+ $this->aliases = $container->getAliases();
- foreach ($container->getAliases() as $id => $alias) {
+ foreach ($this->aliases as $id => $alias) {
$targetId = $this->getDefinitionId((string) $alias);
- $this->graph->connect($id, $alias, $targetId, $this->getDefinition($targetId), null);
+ $this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null);
}
- parent::process($container);
+ try {
+ parent::process($container);
+ } finally {
+ $this->aliases = $this->definitions = [];
+ }
}
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
$lazy = $this->lazy;
+ $inExpression = $this->inExpression();
if ($value instanceof ArgumentInterface) {
- $this->lazy = true;
+ $this->lazy = !$this->byFactory || !$value instanceof IteratorArgument;
parent::processValue($value->getValues());
$this->lazy = $lazy;
return $value;
}
- if ($value instanceof Expression) {
- $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']);
-
- return $value;
- }
if ($value instanceof Reference) {
$targetId = $this->getDefinitionId((string) $value);
- $targetDefinition = $this->getDefinition($targetId);
+ $targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null;
$this->graph->connect(
$this->currentId,
@@ -106,6 +103,18 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
$this->byConstructor
);
+ if ($inExpression) {
+ $this->graph->connect(
+ '.internal.reference_in_expression',
+ null,
+ $targetId,
+ $targetDefinition,
+ $value,
+ $this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
+ true
+ );
+ }
+
return $value;
}
if (!$value instanceof Definition) {
@@ -123,13 +132,47 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
$byConstructor = $this->byConstructor;
$this->byConstructor = $isRoot || $byConstructor;
+
+ $byFactory = $this->byFactory;
+ $this->byFactory = true;
$this->processValue($value->getFactory());
+ $this->byFactory = $byFactory;
$this->processValue($value->getArguments());
+
+ $properties = $value->getProperties();
+ $setters = $value->getMethodCalls();
+
+ // Any references before a "wither" are part of the constructor-instantiation graph
+ $lastWitherIndex = null;
+ foreach ($setters as $k => $call) {
+ if ($call[2] ?? false) {
+ $lastWitherIndex = $k;
+ }
+ }
+
+ if (null !== $lastWitherIndex) {
+ $this->processValue($properties);
+ $setters = $properties = [];
+
+ foreach ($value->getMethodCalls() as $k => $call) {
+ if (null === $lastWitherIndex) {
+ $setters[] = $call;
+ continue;
+ }
+
+ if ($lastWitherIndex === $k) {
+ $lastWitherIndex = null;
+ }
+
+ $this->processValue($call);
+ }
+ }
+
$this->byConstructor = $byConstructor;
if (!$this->onlyConstructorArguments) {
- $this->processValue($value->getProperties());
- $this->processValue($value->getMethodCalls());
+ $this->processValue($properties);
+ $this->processValue($setters);
$this->processValue($value->getConfigurator());
}
$this->lazy = $lazy;
@@ -137,56 +180,12 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
return $value;
}
- /**
- * Returns a service definition given the full name or an alias.
- *
- * @param string $id A full id or alias for a service definition
- *
- * @return Definition|null The definition related to the supplied id
- */
- private function getDefinition($id)
+ private function getDefinitionId(string $id): ?string
{
- return null === $id ? null : $this->container->getDefinition($id);
- }
-
- private function getDefinitionId($id)
- {
- while ($this->container->hasAlias($id)) {
- $id = (string) $this->container->getAlias($id);
+ while (isset($this->aliases[$id])) {
+ $id = (string) $this->aliases[$id];
}
- if (!$this->container->hasDefinition($id)) {
- return null;
- }
-
- return $this->container->normalizeId($id);
- }
-
- private function getExpressionLanguage()
- {
- if (null === $this->expressionLanguage) {
- if (!class_exists(ExpressionLanguage::class)) {
- throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
- }
-
- $providers = $this->container->getExpressionLanguageProviders();
- $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
- if ('""' === substr_replace($arg, '', 1, -1)) {
- $id = stripcslashes(substr($arg, 1, -1));
- $id = $this->getDefinitionId($id);
-
- $this->graph->connect(
- $this->currentId,
- $this->currentDefinition,
- $id,
- $this->getDefinition($id)
- );
- }
-
- return sprintf('$this->get(%s)', $arg);
- });
- }
-
- return $this->expressionLanguage;
+ return isset($this->definitions[$id]) ? $id : null;
}
}
diff --git a/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php b/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php
new file mode 100644
index 000000000..4db7185cf
--- /dev/null
+++ b/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php
@@ -0,0 +1,168 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\LogicException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+
+/**
+ * @author Alexander M. Turek
+ */
+class AutowireRequiredPropertiesPass extends AbstractRecursivePass
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function processValue($value, bool $isRoot = false)
+ {
+ if (\PHP_VERSION_ID < 70400) {
+ return $value;
+ }
+ $value = parent::processValue($value, $isRoot);
+
+ if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
+ return $value;
+ }
+ if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
+ return $value;
+ }
+
+ $properties = $value->getProperties();
+ foreach ($reflectionClass->getProperties() as $reflectionProperty) {
+ if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) {
+ continue;
+ }
+ if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class))
+ && ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc))
+ ) {
+ continue;
+ }
+ if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) {
+ continue;
+ }
+
+ $type = $type->getName();
+ $value->setProperty($name, new TypedReference($type, $type, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name));
+ }
+
+ return $value;
+ }
+}
diff --git a/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php
index 30a6f524a..93808b201 100644
--- a/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php
+++ b/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php
@@ -24,7 +24,7 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass
{
private $throwExceptions;
- public function __construct($throwExceptions = true)
+ public function __construct(bool $throwExceptions = true)
{
$this->throwExceptions = $throwExceptions;
}
@@ -32,14 +32,20 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass
/**
* {@inheritdoc}
*/
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$i = 0;
+ $hasNamedArgs = false;
foreach ($value->getArguments() as $k => $v) {
+ if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) {
+ $hasNamedArgs = true;
+ continue;
+ }
+
if ($k !== $i++) {
if (!\is_int($k)) {
$msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k);
@@ -57,11 +63,27 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass
throw new RuntimeException($msg);
}
}
+
+ if ($hasNamedArgs) {
+ $msg = sprintf('Invalid constructor argument for service "%s": cannot use positional argument after named argument. Check your service definition.', $this->currentId);
+ $value->addError($msg);
+ if ($this->throwExceptions) {
+ throw new RuntimeException($msg);
+ }
+
+ break;
+ }
}
foreach ($value->getMethodCalls() as $methodCall) {
$i = 0;
+ $hasNamedArgs = false;
foreach ($methodCall[1] as $k => $v) {
+ if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) {
+ $hasNamedArgs = true;
+ continue;
+ }
+
if ($k !== $i++) {
if (!\is_int($k)) {
$msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k);
@@ -79,6 +101,16 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass
throw new RuntimeException($msg);
}
}
+
+ if ($hasNamedArgs) {
+ $msg = sprintf('Invalid argument for method call "%s" of service "%s": cannot use positional argument after named argument. Check your service definition.', $methodCall[0], $this->currentId);
+ $value->addError($msg);
+ if ($this->throwExceptions) {
+ throw new RuntimeException($msg);
+ }
+
+ break;
+ }
}
}
diff --git a/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php
index 4b6d277fe..6fe564a00 100644
--- a/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php
+++ b/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Loader\FileLoader;
/**
* This pass validates each definition individually only taking the information
@@ -38,17 +39,17 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
{
foreach ($container->getDefinitions() as $id => $definition) {
// synthetic service is public
- if ($definition->isSynthetic() && !($definition->isPublic() || $definition->isPrivate())) {
+ if ($definition->isSynthetic() && !$definition->isPublic()) {
throw new RuntimeException(sprintf('A synthetic service ("%s") must be public.', $id));
}
// non-synthetic, non-abstract service has class
- if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) {
+ if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && !$definition->hasTag('container.service_locator') && (!$definition->getFactory() || !preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) {
if ($definition->getFactory()) {
throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id));
}
if (class_exists($id) || interface_exists($id, false)) {
- if (0 === strpos($id, '\\') && 1 < substr_count($id, '\\')) {
+ if (str_starts_with($id, '\\') && 1 < substr_count($id, '\\')) {
throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, substr($id, 1)));
}
diff --git a/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php
index 77b35f186..fd3173831 100644
--- a/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php
+++ b/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\DependencyInjection\Compiler;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
@@ -22,15 +23,83 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
{
- protected function processValue($value, $isRoot = false)
+ private $serviceLocatorContextIds = [];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ $this->serviceLocatorContextIds = [];
+ foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) {
+ $this->serviceLocatorContextIds[$id] = $tags[0]['id'];
+ $container->getDefinition($id)->clearTag('container.service_locator_context');
+ }
+
+ try {
+ return parent::process($container);
+ } finally {
+ $this->serviceLocatorContextIds = [];
+ }
+ }
+
+ protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
- if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) {
- throw new ServiceNotFoundException($id, $this->currentId);
+ if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has($id = (string) $value)) {
+ return $value;
}
- return $value;
+ $currentId = $this->currentId;
+ $graph = $this->container->getCompiler()->getServiceReferenceGraph();
+
+ if (isset($this->serviceLocatorContextIds[$currentId])) {
+ $currentId = $this->serviceLocatorContextIds[$currentId];
+ $locator = $this->container->getDefinition($this->currentId)->getFactory()[0];
+
+ foreach ($locator->getArgument(0) as $k => $v) {
+ if ($v->getValues()[0] === $value) {
+ if ($k !== $id) {
+ $currentId = $k.'" in the container provided to "'.$currentId;
+ }
+ throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
+ }
+ }
+ }
+
+ if ('.' === $currentId[0] && $graph->hasNode($currentId)) {
+ foreach ($graph->getNode($currentId)->getInEdges() as $edge) {
+ if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) {
+ continue;
+ }
+ $sourceId = $edge->getSourceNode()->getId();
+
+ if ('.' !== $sourceId[0]) {
+ $currentId = $sourceId;
+ break;
+ }
+ }
+ }
+
+ throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
+ }
+
+ private function getAlternatives(string $id): array
+ {
+ $alternatives = [];
+ foreach ($this->container->getServiceIds() as $knownId) {
+ if ('' === $knownId || '.' === $knownId[0]) {
+ continue;
+ }
+
+ $lev = levenshtein($id, $knownId);
+ if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) {
+ $alternatives[] = $knownId;
+ }
+ }
+
+ return $alternatives;
}
}
diff --git a/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php
index 8f2a3bdf7..0349ef761 100644
--- a/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php
+++ b/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php
@@ -25,7 +25,7 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class CheckReferenceValidityPass extends AbstractRecursivePass
{
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) {
return $value;
diff --git a/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php b/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php
new file mode 100644
index 000000000..b7ec85cef
--- /dev/null
+++ b/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php
@@ -0,0 +1,329 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\ExpressionLanguage;
+use Symfony\Component\DependencyInjection\Parameter;
+use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+use Symfony\Component\ExpressionLanguage\Expression;
+
+/**
+ * Checks whether injected parameters are compatible with type declarations.
+ *
+ * This pass should be run after all optimization passes.
+ *
+ * It can be added either:
+ * * before removing passes to check all services even if they are not currently used,
+ * * after removing passes to check only services are used in the app.
+ *
+ * @author Nicolas Grekas
+ * @author Julien Maulny
+ */
+final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface
+{
+ private static $registerForAutoconfiguration;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (80000 > \PHP_VERSION_ID) {
+ return;
+ }
+
+ foreach ($container->getDefinitions() as $id => $definition) {
+ if ($this->accept($definition) && $class = $container->getReflectionClass($definition->getClass(), false)) {
+ $this->processClass($container, $class);
+ }
+ }
+ }
+
+ public function accept(Definition $definition): bool
+ {
+ return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes');
+ }
+
+ public function processClass(ContainerBuilder $container, \ReflectionClass $class)
+ {
+ foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
+ self::registerForAutoconfiguration($container, $class, $attribute);
+ }
+ }
+
+ private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute)
+ {
+ if (self::$registerForAutoconfiguration) {
+ return (self::$registerForAutoconfiguration)($container, $class, $attribute);
+ }
+
+ $parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions');
+ $parseDefinitions->setAccessible(true);
+ $yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor();
+
+ self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) {
+ $attribute = (array) $attribute->newInstance();
+
+ foreach ($attribute['tags'] ?? [] as $i => $tag) {
+ if (\is_array($tag) && [0] === array_keys($tag)) {
+ $attribute['tags'][$i] = [$class->name => $tag[0]];
+ }
+ }
+
+ $parseDefinitions->invoke(
+ $yamlLoader,
+ [
+ 'services' => [
+ '_instanceof' => [
+ $class->name => [$container->registerForAutoconfiguration($class->name)] + $attribute,
+ ],
+ ],
+ ],
+ $class->getFileName(),
+ false
+ );
+ };
+
+ return (self::$registerForAutoconfiguration)($container, $class, $attribute);
+ }
+}
diff --git a/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php b/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php
index b4d0d0550..251889ebe 100644
--- a/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php
+++ b/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php
@@ -11,14 +11,12 @@
namespace Symfony\Component\DependencyInjection\Compiler;
-use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\EnvVarProcessor;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ServiceLocator;
/**
* Creates the container.env_var_processors_locator service.
@@ -27,7 +25,7 @@ use Symfony\Component\DependencyInjection\ServiceLocator;
*/
class RegisterEnvVarProcessorsPass implements CompilerPassInterface
{
- private static $allowedTypes = ['array', 'bool', 'float', 'int', 'string'];
+ private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string'];
public function process(ContainerBuilder $container)
{
@@ -41,7 +39,7 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
}
foreach ($class::getProvidedTypes() as $prefix => $type) {
- $processors[$prefix] = new ServiceClosureArgument(new Reference($id));
+ $processors[$prefix] = new Reference($id);
$types[$prefix] = self::validateProvidedTypes($type, $class);
}
}
@@ -56,20 +54,19 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface
}
if ($processors) {
- $container->register('container.env_var_processors_locator', ServiceLocator::class)
+ $container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors))
->setPublic(true)
- ->setArguments([$processors])
;
}
}
- private static function validateProvidedTypes($types, $class)
+ private static function validateProvidedTypes(string $types, string $class): array
{
$types = explode('|', $types);
foreach ($types as $type) {
- if (!\in_array($type, self::$allowedTypes)) {
- throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::$allowedTypes)));
+ if (!\in_array($type, self::ALLOWED_TYPES)) {
+ throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::ALLOWED_TYPES)));
}
}
diff --git a/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php b/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php
new file mode 100644
index 000000000..c5eb9bf08
--- /dev/null
+++ b/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas
+ */
+class RegisterReverseContainerPass implements CompilerPassInterface
+{
+ private $beforeRemoving;
+ private $serviceId;
+ private $tagName;
+
+ public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible')
+ {
+ if (1 < \func_num_args()) {
+ trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->beforeRemoving = $beforeRemoving;
+ $this->serviceId = $serviceId;
+ $this->tagName = $tagName;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->serviceId)) {
+ return;
+ }
+
+ $refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
+ $services = [];
+ foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
+ $services[$id] = new Reference($id, $refType);
+ }
+
+ if ($this->beforeRemoving) {
+ // prevent inlining of the reverse container
+ $services[$this->serviceId] = new Reference($this->serviceId, $refType);
+ }
+ $locator = $container->getDefinition($this->serviceId)->getArgument(1);
+
+ if ($locator instanceof Reference) {
+ $locator = $container->getDefinition((string) $locator);
+ }
+ if ($locator instanceof Definition) {
+ foreach ($services as $id => $ref) {
+ $services[$id] = new ServiceClosureArgument($ref);
+ }
+ $locator->replaceArgument(0, $services);
+ } else {
+ $locator->setValues($services);
+ }
+ }
+}
diff --git a/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php b/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php
index bf1387c04..2a458ad12 100644
--- a/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php
+++ b/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php
@@ -11,12 +11,17 @@
namespace Symfony\Component\DependencyInjection\Compiler;
+use Psr\Container\ContainerInterface as PsrContainerInterface;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\DependencyInjection\TypedReference;
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+use Symfony\Contracts\Service\ServiceProviderInterface;
+use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Compiler pass to register tagged services that require a service locator.
@@ -25,7 +30,7 @@ use Symfony\Component\DependencyInjection\TypedReference;
*/
class RegisterServiceSubscribersPass extends AbstractRecursivePass
{
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) {
return parent::processValue($value, $isRoot);
@@ -63,29 +68,47 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class));
}
$class = $r->name;
-
+ $replaceDeprecatedSession = $this->container->has('.session.deprecated') && $r->isSubclassOf(AbstractController::class);
$subscriberMap = [];
- $declaringClass = (new \ReflectionMethod($class, 'getSubscribedServices'))->class;
foreach ($class::getSubscribedServices() as $key => $type) {
- if (!\is_string($type) || !preg_match('/^\??[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $type)) {
- throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : \gettype($type)));
+ if (!\is_string($type) || !preg_match('/(?(DEFINE)(?
*/
-class RemoveUnusedDefinitionsPass implements RepeatablePassInterface
+class RemoveUnusedDefinitionsPass extends AbstractRecursivePass
{
- private $repeatedPass;
-
- /**
- * {@inheritdoc}
- */
- public function setRepeatedPass(RepeatedPass $repeatedPass)
- {
- $this->repeatedPass = $repeatedPass;
- }
+ private $connectedIds = [];
/**
* Processes the ContainerBuilder to remove unused definitions.
*/
public function process(ContainerBuilder $container)
{
- $graph = $container->getCompiler()->getServiceReferenceGraph();
+ try {
+ $this->enableExpressionProcessing();
+ $this->container = $container;
+ $connectedIds = [];
+ $aliases = $container->getAliases();
- $hasChanged = false;
- foreach ($container->getDefinitions() as $id => $definition) {
- if ($definition->isPublic() || $definition->isPrivate()) {
- continue;
+ foreach ($aliases as $id => $alias) {
+ if ($alias->isPublic()) {
+ $this->connectedIds[] = (string) $aliases[$id];
+ }
}
- if ($graph->hasNode($id)) {
- $edges = $graph->getNode($id)->getInEdges();
- $referencingAliases = [];
- $sourceIds = [];
- foreach ($edges as $edge) {
- if ($edge->isWeak()) {
- continue;
- }
- $node = $edge->getSourceNode();
- $sourceIds[] = $node->getId();
+ foreach ($container->getDefinitions() as $id => $definition) {
+ if ($definition->isPublic()) {
+ $connectedIds[$id] = true;
+ $this->processValue($definition);
+ }
+ }
- if ($node->isAlias()) {
- $referencingAliases[] = $node->getValue();
+ while ($this->connectedIds) {
+ $ids = $this->connectedIds;
+ $this->connectedIds = [];
+ foreach ($ids as $id) {
+ if (!isset($connectedIds[$id]) && $container->hasDefinition($id)) {
+ $connectedIds[$id] = true;
+ $this->processValue($container->getDefinition($id));
}
}
- $isReferenced = (\count(array_unique($sourceIds)) - \count($referencingAliases)) > 0;
- } else {
- $referencingAliases = [];
- $isReferenced = false;
}
- if (1 === \count($referencingAliases) && false === $isReferenced) {
- $container->setDefinition((string) reset($referencingAliases), $definition);
- $definition->setPublic(!$definition->isPrivate());
- $definition->setPrivate(reset($referencingAliases)->isPrivate());
- $container->removeDefinition($id);
- $container->log($this, sprintf('Removed service "%s"; reason: replaces alias %s.', $id, reset($referencingAliases)));
- } elseif (0 === \count($referencingAliases) && false === $isReferenced) {
- $container->removeDefinition($id);
- $container->resolveEnvPlaceholders(serialize($definition));
- $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id));
- $hasChanged = true;
+ foreach ($container->getDefinitions() as $id => $definition) {
+ if (!isset($connectedIds[$id])) {
+ $container->removeDefinition($id);
+ $container->resolveEnvPlaceholders(!$definition->hasErrors() ? serialize($definition) : $definition);
+ $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id));
+ }
}
- }
-
- if ($hasChanged) {
- $this->repeatedPass->setRepeat();
+ } finally {
+ $this->container = null;
+ $this->connectedIds = [];
}
}
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function processValue($value, bool $isRoot = false)
+ {
+ if (!$value instanceof Reference) {
+ return parent::processValue($value, $isRoot);
+ }
+
+ if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) {
+ $this->connectedIds[] = (string) $value;
+ }
+
+ return $value;
+ }
}
diff --git a/lib/symfony/dependency-injection/Compiler/RepeatablePassInterface.php b/lib/symfony/dependency-injection/Compiler/RepeatablePassInterface.php
deleted file mode 100644
index 2b88bfb91..000000000
--- a/lib/symfony/dependency-injection/Compiler/RepeatablePassInterface.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\DependencyInjection\Compiler;
-
-/**
- * Interface that must be implemented by passes that are run as part of an
- * RepeatedPass.
- *
- * @author Johannes M. Schmitt
+ */
+class ResolveDecoratorStackPass implements CompilerPassInterface
+{
+ private $tag;
+
+ public function __construct(string $tag = 'container.stack')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->tag = $tag;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ $stacks = [];
+
+ foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) {
+ $definition = $container->getDefinition($id);
+
+ if (!$definition instanceof ChildDefinition) {
+ throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag));
+ }
+
+ if (!$stack = $definition->getArguments()) {
+ throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id));
+ }
+
+ $stacks[$id] = $stack;
+ }
+
+ if (!$stacks) {
+ return;
+ }
+
+ $resolvedDefinitions = [];
+
+ foreach ($container->getDefinitions() as $id => $definition) {
+ if (!isset($stacks[$id])) {
+ $resolvedDefinitions[$id] = $definition;
+ continue;
+ }
+
+ foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) {
+ $resolvedDefinitions[$k] = $v;
+ }
+
+ $alias = $container->setAlias($id, $k);
+
+ if ($definition->getChanges()['public'] ?? false) {
+ $alias->setPublic($definition->isPublic());
+ }
+
+ if ($definition->isDeprecated()) {
+ $alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%')));
+ }
+ }
+
+ $container->setDefinitions($resolvedDefinitions);
+ }
+
+ private function resolveStack(array $stacks, array $path): array
+ {
+ $definitions = [];
+ $id = end($path);
+ $prefix = '.'.$id.'.';
+
+ if (!isset($stacks[$id])) {
+ return [$id => new ChildDefinition($id)];
+ }
+
+ if (key($path) !== $searchKey = array_search($id, $path)) {
+ throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey));
+ }
+
+ foreach ($stacks[$id] as $k => $definition) {
+ if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) {
+ $path[] = $definition->getParent();
+ $definition = unserialize(serialize($definition)); // deep clone
+ } elseif ($definition instanceof Definition) {
+ $definitions[$decoratedId = $prefix.$k] = $definition;
+ continue;
+ } elseif ($definition instanceof Reference || $definition instanceof Alias) {
+ $path[] = (string) $definition;
+ } else {
+ throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition)));
+ }
+
+ $p = $prefix.$k;
+
+ foreach ($this->resolveStack($stacks, $path) as $k => $v) {
+ $definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k);
+ $definition = null;
+ }
+ array_pop($path);
+ }
+
+ if (1 === \count($path)) {
+ foreach ($definitions as $k => $definition) {
+ $definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId);
+ }
+ $definition->setDecoratedService(null);
+ }
+
+ return $definitions;
+ }
+}
diff --git a/lib/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php
deleted file mode 100644
index 79fca8d5e..000000000
--- a/lib/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php
+++ /dev/null
@@ -1,29 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\DependencyInjection\Compiler;
-
-@trigger_error('The '.__NAMESPACE__.'\ResolveDefinitionTemplatesPass class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the ResolveChildDefinitionsPass class instead.', \E_USER_DEPRECATED);
-
-class_exists(ResolveChildDefinitionsPass::class);
-
-if (false) {
- /**
- * This definition decorates another definition.
- *
- * @author Johannes M. Schmitt
+ */
+class ResolveNoPreloadPass extends AbstractRecursivePass
+{
+ private const DO_PRELOAD_TAG = '.container.do_preload';
+
+ private $tagName;
+ private $resolvedIds = [];
+
+ public function __construct(string $tagName = 'container.no_preload')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->tagName = $tagName;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ $this->container = $container;
+
+ try {
+ foreach ($container->getDefinitions() as $id => $definition) {
+ if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) {
+ $this->resolvedIds[$id] = true;
+ $this->processValue($definition, true);
+ }
+ }
+
+ foreach ($container->getAliases() as $alias) {
+ if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->hasDefinition($id)) {
+ $this->resolvedIds[$id] = true;
+ $this->processValue($container->getDefinition($id), true);
+ }
+ }
+ } finally {
+ $this->resolvedIds = [];
+ $this->container = null;
+ }
+
+ foreach ($container->getDefinitions() as $definition) {
+ if ($definition->hasTag(self::DO_PRELOAD_TAG)) {
+ $definition->clearTag(self::DO_PRELOAD_TAG);
+ } elseif (!$definition->isDeprecated() && !$definition->hasErrors()) {
+ $definition->addTag($this->tagName);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function processValue($value, bool $isRoot = false)
+ {
+ if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) {
+ $definition = $this->container->getDefinition($id);
+
+ if (!isset($this->resolvedIds[$id]) && (!$definition->isPublic() || $definition->isPrivate())) {
+ $this->resolvedIds[$id] = true;
+ $this->processValue($definition, true);
+ }
+
+ return $value;
+ }
+
+ if (!$value instanceof Definition) {
+ return parent::processValue($value, $isRoot);
+ }
+
+ if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) {
+ return $value;
+ }
+
+ if ($isRoot) {
+ $value->addTag(self::DO_PRELOAD_TAG);
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+}
diff --git a/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php
index 32eb6a3a7..0099a3bbc 100644
--- a/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php
+++ b/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php
@@ -60,7 +60,7 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass
$this->bag = null;
}
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
if (\is_string($value)) {
try {
@@ -85,6 +85,11 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass
if (isset($changes['file'])) {
$value->setFile($this->bag->resolveValue($value->getFile()));
}
+ $tags = $value->getTags();
+ if (isset($tags['proxy'])) {
+ $tags['proxy'] = $this->bag->resolveValue($tags['proxy']);
+ $value->setTags($tags);
+ }
}
$value = parent::processValue($value, $isRoot);
diff --git a/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php b/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php
index 1bd993458..b63e3f5c2 100644
--- a/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php
+++ b/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php
@@ -11,10 +11,14 @@
namespace Symfony\Component\DependencyInjection\Compiler;
+trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s" class is deprecated.', ResolvePrivatesPass::class);
+
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Nicolas Grekas
+ *
+ * @deprecated since Symfony 5.2
*/
class ResolvePrivatesPass implements CompilerPassInterface
{
diff --git a/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php
index 2559dcf10..e59893ff7 100644
--- a/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php
+++ b/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php
@@ -30,9 +30,11 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass
parent::process($container);
foreach ($container->getAliases() as $id => $alias) {
- $aliasId = $container->normalizeId($alias);
+ $aliasId = (string) $alias;
+ $this->currentId = $id;
+
if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) {
- $container->setAlias($id, $defId)->setPublic($alias->isPublic())->setPrivate($alias->isPrivate());
+ $container->setAlias($id, $defId)->setPublic($alias->isPublic());
}
}
}
@@ -40,36 +42,42 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass
/**
* {@inheritdoc}
*/
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
- if ($value instanceof Reference) {
- $defId = $this->getDefinitionId($id = $this->container->normalizeId($value), $this->container);
+ if (!$value instanceof Reference) {
+ return parent::processValue($value, $isRoot);
+ }
- if ($defId !== $id) {
- return new Reference($defId, $value->getInvalidBehavior());
+ $defId = $this->getDefinitionId($id = (string) $value, $this->container);
+
+ return $defId !== $id ? new Reference($defId, $value->getInvalidBehavior()) : $value;
+ }
+
+ private function getDefinitionId(string $id, ContainerBuilder $container): string
+ {
+ if (!$container->hasAlias($id)) {
+ return $id;
+ }
+
+ $alias = $container->getAlias($id);
+
+ if ($alias->isDeprecated()) {
+ $referencingDefinition = $container->hasDefinition($this->currentId) ? $container->getDefinition($this->currentId) : $container->getAlias($this->currentId);
+ if (!$referencingDefinition->isDeprecated()) {
+ $deprecation = $alias->getDeprecation($id);
+ trigger_deprecation($deprecation['package'], $deprecation['version'], rtrim($deprecation['message'], '. ').'. It is being referenced by the "%s" '.($container->hasDefinition($this->currentId) ? 'service.' : 'alias.'), $this->currentId);
}
}
- return parent::processValue($value);
- }
-
- /**
- * Resolves an alias into a definition id.
- *
- * @param string $id The definition or alias id to resolve
- *
- * @return string The definition id with aliases resolved
- */
- private function getDefinitionId($id, ContainerBuilder $container)
- {
$seen = [];
- while ($container->hasAlias($id)) {
+ do {
if (isset($seen[$id])) {
throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), [$id]));
}
+
$seen[$id] = true;
- $id = $container->normalizeId($container->getAlias($id));
- }
+ $id = (string) $container->getAlias($id);
+ } while ($container->hasAlias($id));
return $id;
}
diff --git a/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php
index ccc80a443..518c03d7e 100644
--- a/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php
+++ b/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* Compiler pass to inject their service locator to service subscribers.
@@ -24,9 +25,9 @@ class ResolveServiceSubscribersPass extends AbstractRecursivePass
{
private $serviceLocator;
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
- if ($value instanceof Reference && $this->serviceLocator && ContainerInterface::class === $this->container->normalizeId($value)) {
+ if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) {
return new Reference($this->serviceLocator);
}
diff --git a/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php b/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php
index 009cee9bf..48a034a84 100644
--- a/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php
+++ b/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php
@@ -25,13 +25,13 @@ class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass
/**
* {@inheritdoc}
*/
- protected function processValue($value, $isRoot = false)
+ protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof TaggedIteratorArgument) {
return parent::processValue($value, $isRoot);
}
- $value->setValues($this->findAndSortTaggedServices($value->getTag(), $this->container));
+ $value->setValues($this->findAndSortTaggedServices($value, $this->container));
return $value;
}
diff --git a/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php b/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php
index a7427c5a5..faa7b57e4 100644
--- a/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php
+++ b/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php
@@ -13,6 +13,8 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -26,8 +28,18 @@ use Symfony\Component\DependencyInjection\ServiceLocator;
*/
final class ServiceLocatorTagPass extends AbstractRecursivePass
{
- protected function processValue($value, $isRoot = false)
+ use PriorityTaggedServiceTrait;
+
+ protected function processValue($value, bool $isRoot = false)
{
+ if ($value instanceof ServiceLocatorArgument) {
+ if ($value->getTaggedIteratorArgument()) {
+ $value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container));
+ }
+
+ return self::register($this->container, $value->getValues());
+ }
+
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
return parent::processValue($value, $isRoot);
}
@@ -36,36 +48,41 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass
$value->setClass(ServiceLocator::class);
}
- $arguments = $value->getArguments();
- if (!isset($arguments[0]) || !\is_array($arguments[0])) {
+ $services = $value->getArguments()[0] ?? null;
+
+ if ($services instanceof TaggedIteratorArgument) {
+ $services = $this->findAndSortTaggedServices($services, $this->container);
+ }
+
+ if (!\is_array($services)) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId));
}
$i = 0;
- foreach ($arguments[0] as $k => $v) {
+ foreach ($services as $k => $v) {
if ($v instanceof ServiceClosureArgument) {
continue;
}
if (!$v instanceof Reference) {
- throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, \is_object($v) ? \get_class($v) : \gettype($v), $k));
+ throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, get_debug_type($v), $k));
}
if ($i === $k) {
- unset($arguments[0][$k]);
+ unset($services[$k]);
$k = (string) $v;
++$i;
} elseif (\is_int($k)) {
$i = null;
}
- $arguments[0][$k] = new ServiceClosureArgument($v);
+ $services[$k] = new ServiceClosureArgument($v);
}
- ksort($arguments[0]);
+ ksort($services);
- $value->setArguments($arguments);
+ $value->setArgument(0, $services);
- $id = 'service_locator.'.ContainerBuilder::hash($value);
+ $id = '.service_locator.'.ContainerBuilder::hash($value);
if ($isRoot) {
if ($id !== $this->currentId) {
@@ -82,30 +99,25 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass
/**
* @param Reference[] $refMap
- * @param string|null $callerId
- *
- * @return Reference
*/
- public static function register(ContainerBuilder $container, array $refMap, $callerId = null)
+ public static function register(ContainerBuilder $container, array $refMap, string $callerId = null): Reference
{
foreach ($refMap as $id => $ref) {
if (!$ref instanceof Reference) {
- throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', \is_object($ref) ? \get_class($ref) : \gettype($ref), $id));
+ throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', get_debug_type($ref), $id));
}
$refMap[$id] = new ServiceClosureArgument($ref);
}
- ksort($refMap);
$locator = (new Definition(ServiceLocator::class))
->addArgument($refMap)
- ->setPublic(false)
->addTag('container.service_locator');
if (null !== $callerId && $container->hasDefinition($callerId)) {
$locator->setBindings($container->getDefinition($callerId)->getBindings());
}
- if (!$container->hasDefinition($id = 'service_locator.'.ContainerBuilder::hash($locator))) {
+ if (!$container->hasDefinition($id = '.service_locator.'.ContainerBuilder::hash($locator))) {
$container->setDefinition($id, $locator);
}
@@ -115,8 +127,8 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass
// to have them specialized per consumer service, we use a cloning factory
// to derivate customized instances from the prototype one.
$container->register($id .= '.'.$callerId, ServiceLocator::class)
- ->setPublic(false)
->setFactory([new Reference($locatorId), 'withContext'])
+ ->addTag('container.service_locator_context', ['id' => $callerId])
->addArgument($callerId)
->addArgument(new Reference('service_container'));
}
diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php
index e419e297e..1225514c2 100644
--- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php
+++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
/**
* This is a directed graph of your services.
@@ -21,7 +22,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
*
* @author Johannes M. Schmitt
+ * ╔═══════════════╤══════════════════════════╤══════════════════╗
+ * 1 ISBN 2 Title │ Author ║
+ * ╠═══════════════╪══════════════════════════╪══════════════════╣
+ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
+ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
+ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
+ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
+ * ╚═══════════════╧══════════════════════════╧══════════════════╝
+ *
*
* @return $this
*/
- public function setHorizontalBorderChar($horizontalBorderChar)
+ public function setHorizontalBorderChars(string $outside, string $inside = null): self
{
- $this->horizontalBorderChar = $horizontalBorderChar;
+ $this->horizontalOutsideBorderChar = $outside;
+ $this->horizontalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
- * Gets horizontal border character.
+ * Sets vertical border characters.
*
- * @return string
- */
- public function getHorizontalBorderChar()
- {
- return $this->horizontalBorderChar;
- }
-
- /**
- * Sets vertical border character.
- *
- * @param string $verticalBorderChar
+ *
+ * ╔═══════════════╤══════════════════════════╤══════════════════╗
+ * ║ ISBN │ Title │ Author ║
+ * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
+ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
+ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
+ * ╟───────2───────┼──────────────────────────┼──────────────────╢
+ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
+ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
+ * ╚═══════════════╧══════════════════════════╧══════════════════╝
+ *
*
* @return $this
*/
- public function setVerticalBorderChar($verticalBorderChar)
+ public function setVerticalBorderChars(string $outside, string $inside = null): self
{
- $this->verticalBorderChar = $verticalBorderChar;
+ $this->verticalOutsideBorderChar = $outside;
+ $this->verticalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
- * Gets vertical border character.
+ * Gets border characters.
*
- * @return string
+ * @internal
*/
- public function getVerticalBorderChar()
+ public function getBorderChars(): array
{
- return $this->verticalBorderChar;
+ return [
+ $this->horizontalOutsideBorderChar,
+ $this->verticalOutsideBorderChar,
+ $this->horizontalInsideBorderChar,
+ $this->verticalInsideBorderChar,
+ ];
}
/**
- * Sets crossing character.
+ * Sets crossing characters.
*
- * @param string $crossingChar
+ * Example:
+ *
+ * 1═══════════════2══════════════════════════2══════════════════3
+ * ║ ISBN │ Title │ Author ║
+ * 8'══════════════0'═════════════════════════0'═════════════════4'
+ * ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
+ * ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
+ * 8───────────────0──────────────────────────0──────────────────4
+ * ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
+ * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
+ * 7═══════════════6══════════════════════════6══════════════════5
+ *
+ *
+ * @param string $cross Crossing char (see #0 of example)
+ * @param string $topLeft Top left char (see #1 of example)
+ * @param string $topMid Top mid char (see #2 of example)
+ * @param string $topRight Top right char (see #3 of example)
+ * @param string $midRight Mid right char (see #4 of example)
+ * @param string $bottomRight Bottom right char (see #5 of example)
+ * @param string $bottomMid Bottom mid char (see #6 of example)
+ * @param string $bottomLeft Bottom left char (see #7 of example)
+ * @param string $midLeft Mid left char (see #8 of example)
+ * @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null
+ * @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null
+ * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
*
* @return $this
*/
- public function setCrossingChar($crossingChar)
+ public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self
{
- $this->crossingChar = $crossingChar;
+ $this->crossingChar = $cross;
+ $this->crossingTopLeftChar = $topLeft;
+ $this->crossingTopMidChar = $topMid;
+ $this->crossingTopRightChar = $topRight;
+ $this->crossingMidRightChar = $midRight;
+ $this->crossingBottomRightChar = $bottomRight;
+ $this->crossingBottomMidChar = $bottomMid;
+ $this->crossingBottomLeftChar = $bottomLeft;
+ $this->crossingMidLeftChar = $midLeft;
+ $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
+ $this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
+ $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;
return $this;
}
+ /**
+ * Sets default crossing character used for each cross.
+ *
+ * @see {@link setCrossingChars()} for setting each crossing individually.
+ */
+ public function setDefaultCrossingChar(string $char): self
+ {
+ return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
+ }
+
/**
* Gets crossing character.
*
@@ -133,13 +208,34 @@ class TableStyle
}
/**
- * Sets header cell format.
+ * Gets crossing characters.
*
- * @param string $cellHeaderFormat
+ * @internal
+ */
+ public function getCrossingChars(): array
+ {
+ return [
+ $this->crossingChar,
+ $this->crossingTopLeftChar,
+ $this->crossingTopMidChar,
+ $this->crossingTopRightChar,
+ $this->crossingMidRightChar,
+ $this->crossingBottomRightChar,
+ $this->crossingBottomMidChar,
+ $this->crossingBottomLeftChar,
+ $this->crossingMidLeftChar,
+ $this->crossingTopLeftBottomChar,
+ $this->crossingTopMidBottomChar,
+ $this->crossingTopRightBottomChar,
+ ];
+ }
+
+ /**
+ * Sets header cell format.
*
* @return $this
*/
- public function setCellHeaderFormat($cellHeaderFormat)
+ public function setCellHeaderFormat(string $cellHeaderFormat)
{
$this->cellHeaderFormat = $cellHeaderFormat;
@@ -159,11 +255,9 @@ class TableStyle
/**
* Sets row cell format.
*
- * @param string $cellRowFormat
- *
* @return $this
*/
- public function setCellRowFormat($cellRowFormat)
+ public function setCellRowFormat(string $cellRowFormat)
{
$this->cellRowFormat = $cellRowFormat;
@@ -183,11 +277,9 @@ class TableStyle
/**
* Sets row cell content format.
*
- * @param string $cellRowContentFormat
- *
* @return $this
*/
- public function setCellRowContentFormat($cellRowContentFormat)
+ public function setCellRowContentFormat(string $cellRowContentFormat)
{
$this->cellRowContentFormat = $cellRowContentFormat;
@@ -207,11 +299,9 @@ class TableStyle
/**
* Sets table border format.
*
- * @param string $borderFormat
- *
* @return $this
*/
- public function setBorderFormat($borderFormat)
+ public function setBorderFormat(string $borderFormat)
{
$this->borderFormat = $borderFormat;
@@ -231,11 +321,9 @@ class TableStyle
/**
* Sets cell padding type.
*
- * @param int $padType STR_PAD_*
- *
* @return $this
*/
- public function setPadType($padType)
+ public function setPadType(int $padType)
{
if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
@@ -255,4 +343,34 @@ class TableStyle
{
return $this->padType;
}
+
+ public function getHeaderTitleFormat(): string
+ {
+ return $this->headerTitleFormat;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setHeaderTitleFormat(string $format): self
+ {
+ $this->headerTitleFormat = $format;
+
+ return $this;
+ }
+
+ public function getFooterTitleFormat(): string
+ {
+ return $this->footerTitleFormat;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setFooterTitleFormat(string $format): self
+ {
+ $this->footerTitleFormat = $format;
+
+ return $this;
+ }
}
diff --git a/lib/symfony/console/Input/ArgvInput.php b/lib/symfony/console/Input/ArgvInput.php
index fb2b3584f..675b9ef58 100644
--- a/lib/symfony/console/Input/ArgvInput.php
+++ b/lib/symfony/console/Input/ArgvInput.php
@@ -43,13 +43,9 @@ class ArgvInput extends Input
private $tokens;
private $parsed;
- /**
- * @param array|null $argv An array of parameters from the CLI (in the argv format)
- * @param InputDefinition|null $definition A InputDefinition instance
- */
public function __construct(array $argv = null, InputDefinition $definition = null)
{
- $argv = null !== $argv ? $argv : (isset($_SERVER['argv']) ? $_SERVER['argv'] : []);
+ $argv = $argv ?? $_SERVER['argv'] ?? [];
// strip the application name
array_shift($argv);
@@ -72,26 +68,31 @@ class ArgvInput extends Input
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
- if ($parseOptions && '' == $token) {
- $this->parseArgument($token);
- } elseif ($parseOptions && '--' == $token) {
- $parseOptions = false;
- } elseif ($parseOptions && 0 === strpos($token, '--')) {
- $this->parseLongOption($token);
- } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
- $this->parseShortOption($token);
- } else {
- $this->parseArgument($token);
- }
+ $parseOptions = $this->parseToken($token, $parseOptions);
}
}
+ protected function parseToken(string $token, bool $parseOptions): bool
+ {
+ if ($parseOptions && '' == $token) {
+ $this->parseArgument($token);
+ } elseif ($parseOptions && '--' == $token) {
+ return false;
+ } elseif ($parseOptions && str_starts_with($token, '--')) {
+ $this->parseLongOption($token);
+ } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
+ $this->parseShortOption($token);
+ } else {
+ $this->parseArgument($token);
+ }
+
+ return $parseOptions;
+ }
+
/**
* Parses a short option.
- *
- * @param string $token The current token
*/
- private function parseShortOption($token)
+ private function parseShortOption(string $token)
{
$name = substr($token, 1);
@@ -110,11 +111,9 @@ class ArgvInput extends Input
/**
* Parses a short option set.
*
- * @param string $name The current token
- *
* @throws RuntimeException When option given doesn't exist
*/
- private function parseShortOptionSet($name)
+ private function parseShortOptionSet(string $name)
{
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
@@ -136,20 +135,13 @@ class ArgvInput extends Input
/**
* Parses a long option.
- *
- * @param string $token The current token
*/
- private function parseLongOption($token)
+ private function parseLongOption(string $token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
- if (0 === \strlen($value = substr($name, $pos + 1))) {
- // if no value after "=" then substr() returns "" since php7 only, false before
- // see https://php.net/migration70.incompatible.php#119151
- if (\PHP_VERSION_ID < 70000 && false === $value) {
- $value = '';
- }
+ if ('' === $value = substr($name, $pos + 1)) {
array_unshift($this->parsed, $value);
}
$this->addLongOption(substr($name, 0, $pos), $value);
@@ -161,11 +153,9 @@ class ArgvInput extends Input
/**
* Parses an argument.
*
- * @param string $token The current token
- *
* @throws RuntimeException When too many arguments are given
*/
- private function parseArgument($token)
+ private function parseArgument(string $token)
{
$c = \count($this->arguments);
@@ -182,23 +172,34 @@ class ArgvInput extends Input
// unexpected argument
} else {
$all = $this->definition->getArguments();
- if (\count($all)) {
- throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))));
+ $symfonyCommandName = null;
+ if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) {
+ $symfonyCommandName = $this->arguments['command'] ?? null;
+ unset($all[$key]);
}
- throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token));
+ if (\count($all)) {
+ if ($symfonyCommandName) {
+ $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
+ } else {
+ $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
+ }
+ } elseif ($symfonyCommandName) {
+ $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
+ } else {
+ $message = sprintf('No arguments expected, got "%s".', $token);
+ }
+
+ throw new RuntimeException($message);
}
}
/**
* Adds a short option value.
*
- * @param string $shortcut The short option key
- * @param mixed $value The value for the option
- *
* @throws RuntimeException When option given doesn't exist
*/
- private function addShortOption($shortcut, $value)
+ private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -210,15 +211,22 @@ class ArgvInput extends Input
/**
* Adds a long option value.
*
- * @param string $name The long option key
- * @param mixed $value The value for the option
- *
* @throws RuntimeException When option given doesn't exist
*/
- private function addLongOption($name, $value)
+ private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
- throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
+ if (!$this->definition->hasNegation($name)) {
+ throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ $optionName = $this->definition->negationToName($name);
+ if (null !== $value) {
+ throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
+ }
+ $this->options[$optionName] = false;
+
+ return;
}
$option = $this->definition->getOption($name);
@@ -263,7 +271,7 @@ class ArgvInput extends Input
$isOption = false;
foreach ($this->tokens as $i => $token) {
if ($token && '-' === $token[0]) {
- if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
+ if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) {
continue;
}
@@ -293,7 +301,7 @@ class ArgvInput extends Input
/**
* {@inheritdoc}
*/
- public function hasParameterOption($values, $onlyParams = false)
+ public function hasParameterOption($values, bool $onlyParams = false)
{
$values = (array) $values;
@@ -305,8 +313,8 @@ class ArgvInput extends Input
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
- $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
- if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
+ $leading = str_starts_with($value, '--') ? $value.'=' : $value;
+ if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) {
return true;
}
}
@@ -318,7 +326,7 @@ class ArgvInput extends Input
/**
* {@inheritdoc}
*/
- public function getParameterOption($values, $default = false, $onlyParams = false)
+ public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
$values = (array) $values;
$tokens = $this->tokens;
@@ -336,8 +344,8 @@ class ArgvInput extends Input
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
- $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
- if ('' !== $leading && 0 === strpos($token, $leading)) {
+ $leading = str_starts_with($value, '--') ? $value.'=' : $value;
+ if ('' !== $leading && str_starts_with($token, $leading)) {
return substr($token, \strlen($leading));
}
}
diff --git a/lib/symfony/console/Input/ArrayInput.php b/lib/symfony/console/Input/ArrayInput.php
index b2ebc7c22..c65161484 100644
--- a/lib/symfony/console/Input/ArrayInput.php
+++ b/lib/symfony/console/Input/ArrayInput.php
@@ -53,7 +53,7 @@ class ArrayInput extends Input
/**
* {@inheritdoc}
*/
- public function hasParameterOption($values, $onlyParams = false)
+ public function hasParameterOption($values, bool $onlyParams = false)
{
$values = (array) $values;
@@ -77,7 +77,7 @@ class ArrayInput extends Input
/**
* {@inheritdoc}
*/
- public function getParameterOption($values, $default = false, $onlyParams = false)
+ public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
$values = (array) $values;
@@ -108,12 +108,13 @@ class ArrayInput extends Input
$params = [];
foreach ($this->parameters as $param => $val) {
if ($param && \is_string($param) && '-' === $param[0]) {
+ $glue = ('-' === $param[1]) ? '=' : ' ';
if (\is_array($val)) {
foreach ($val as $v) {
- $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
+ $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : '');
}
} else {
- $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
+ $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
@@ -132,9 +133,9 @@ class ArrayInput extends Input
if ('--' === $key) {
return;
}
- if (0 === strpos($key, '--')) {
+ if (str_starts_with($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
- } elseif (0 === strpos($key, '-')) {
+ } elseif (str_starts_with($key, '-')) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
@@ -145,12 +146,9 @@ class ArrayInput extends Input
/**
* Adds a short option value.
*
- * @param string $shortcut The short option key
- * @param mixed $value The value for the option
- *
* @throws InvalidOptionException When option given doesn't exist
*/
- private function addShortOption($shortcut, $value)
+ private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
@@ -162,16 +160,20 @@ class ArrayInput extends Input
/**
* Adds a long option value.
*
- * @param string $name The long option key
- * @param mixed $value The value for the option
- *
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
- private function addLongOption($name, $value)
+ private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
- throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
+ if (!$this->definition->hasNegation($name)) {
+ throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
+ }
+
+ $optionName = $this->definition->negationToName($name);
+ $this->options[$optionName] = false;
+
+ return;
}
$option = $this->definition->getOption($name);
@@ -192,8 +194,8 @@ class ArrayInput extends Input
/**
* Adds an argument value.
*
- * @param string $name The argument name
- * @param mixed $value The value for the argument
+ * @param string|int $name The argument name
+ * @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
diff --git a/lib/symfony/console/Input/Input.php b/lib/symfony/console/Input/Input.php
index c1220316d..d37460ed3 100644
--- a/lib/symfony/console/Input/Input.php
+++ b/lib/symfony/console/Input/Input.php
@@ -88,9 +88,9 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* {@inheritdoc}
*/
- public function setInteractive($interactive)
+ public function setInteractive(bool $interactive)
{
- $this->interactive = (bool) $interactive;
+ $this->interactive = $interactive;
}
/**
@@ -104,19 +104,19 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* {@inheritdoc}
*/
- public function getArgument($name)
+ public function getArgument(string $name)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
- return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
+ return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
}
/**
* {@inheritdoc}
*/
- public function setArgument($name, $value)
+ public function setArgument(string $name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
@@ -128,7 +128,7 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* {@inheritdoc}
*/
- public function hasArgument($name)
+ public function hasArgument(string $name)
{
return $this->definition->hasArgument($name);
}
@@ -144,8 +144,16 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* {@inheritdoc}
*/
- public function getOption($name)
+ public function getOption(string $name)
{
+ if ($this->definition->hasNegation($name)) {
+ if (null === $value = $this->getOption($this->definition->negationToName($name))) {
+ return $value;
+ }
+
+ return !$value;
+ }
+
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
@@ -156,9 +164,13 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* {@inheritdoc}
*/
- public function setOption($name, $value)
+ public function setOption(string $name, $value)
{
- if (!$this->definition->hasOption($name)) {
+ if ($this->definition->hasNegation($name)) {
+ $this->options[$this->definition->negationToName($name)] = !$value;
+
+ return;
+ } elseif (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
@@ -168,19 +180,17 @@ abstract class Input implements InputInterface, StreamableInputInterface
/**
* {@inheritdoc}
*/
- public function hasOption($name)
+ public function hasOption(string $name)
{
- return $this->definition->hasOption($name);
+ return $this->definition->hasOption($name) || $this->definition->hasNegation($name);
}
/**
* Escapes a token through escapeshellarg if it contains unsafe chars.
*
- * @param string $token
- *
* @return string
*/
- public function escapeToken($token)
+ public function escapeToken(string $token)
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
diff --git a/lib/symfony/console/Input/InputArgument.php b/lib/symfony/console/Input/InputArgument.php
index 4c2b6a66c..ecfcdad58 100644
--- a/lib/symfony/console/Input/InputArgument.php
+++ b/lib/symfony/console/Input/InputArgument.php
@@ -21,9 +21,9 @@ use Symfony\Component\Console\Exception\LogicException;
*/
class InputArgument
{
- const REQUIRED = 1;
- const OPTIONAL = 2;
- const IS_ARRAY = 4;
+ public const REQUIRED = 1;
+ public const OPTIONAL = 2;
+ public const IS_ARRAY = 4;
private $name;
private $mode;
@@ -31,18 +31,18 @@ class InputArgument
private $description;
/**
- * @param string $name The argument name
- * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
- * @param string $description A description text
- * @param string|string[]|null $default The default value (for self::OPTIONAL mode only)
+ * @param string $name The argument name
+ * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
+ * @param string $description A description text
+ * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
- public function __construct($name, $mode = null, $description = '', $default = null)
+ public function __construct(string $name, int $mode = null, string $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
- } elseif (!\is_int($mode) || $mode > 7 || $mode < 1) {
+ } elseif ($mode > 7 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
@@ -56,7 +56,7 @@ class InputArgument
/**
* Returns the argument name.
*
- * @return string The argument name
+ * @return string
*/
public function getName()
{
@@ -86,13 +86,13 @@ class InputArgument
/**
* Sets the default value.
*
- * @param string|string[]|null $default The default value
+ * @param string|bool|int|float|array|null $default
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
- if (self::REQUIRED === $this->mode && null !== $default) {
+ if ($this->isRequired() && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
@@ -110,7 +110,7 @@ class InputArgument
/**
* Returns the default value.
*
- * @return string|string[]|null The default value
+ * @return string|bool|int|float|array|null
*/
public function getDefault()
{
@@ -120,7 +120,7 @@ class InputArgument
/**
* Returns the description text.
*
- * @return string The description text
+ * @return string
*/
public function getDescription()
{
diff --git a/lib/symfony/console/Input/InputDefinition.php b/lib/symfony/console/Input/InputDefinition.php
index d953f9bba..11f704f0e 100644
--- a/lib/symfony/console/Input/InputDefinition.php
+++ b/lib/symfony/console/Input/InputDefinition.php
@@ -30,9 +30,10 @@ class InputDefinition
{
private $arguments;
private $requiredCount;
- private $hasAnArrayArgument = false;
- private $hasOptional;
+ private $lastArrayArgument;
+ private $lastOptionalArgument;
private $options;
+ private $negations;
private $shortcuts;
/**
@@ -67,12 +68,12 @@ class InputDefinition
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
- public function setArguments($arguments = [])
+ public function setArguments(array $arguments = [])
{
$this->arguments = [];
$this->requiredCount = 0;
- $this->hasOptional = false;
- $this->hasAnArrayArgument = false;
+ $this->lastOptionalArgument = null;
+ $this->lastArrayArgument = null;
$this->addArguments($arguments);
}
@@ -81,7 +82,7 @@ class InputDefinition
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
- public function addArguments($arguments = [])
+ public function addArguments(?array $arguments = [])
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
@@ -99,22 +100,22 @@ class InputDefinition
throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
}
- if ($this->hasAnArrayArgument) {
- throw new LogicException('Cannot add an argument after an array argument.');
+ if (null !== $this->lastArrayArgument) {
+ throw new LogicException(sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName()));
}
- if ($argument->isRequired() && $this->hasOptional) {
- throw new LogicException('Cannot add a required argument after an optional one.');
+ if ($argument->isRequired() && null !== $this->lastOptionalArgument) {
+ throw new LogicException(sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName()));
}
if ($argument->isArray()) {
- $this->hasAnArrayArgument = true;
+ $this->lastArrayArgument = $argument;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
- $this->hasOptional = true;
+ $this->lastOptionalArgument = $argument;
}
$this->arguments[$argument->getName()] = $argument;
@@ -125,7 +126,7 @@ class InputDefinition
*
* @param string|int $name The InputArgument name or position
*
- * @return InputArgument An InputArgument object
+ * @return InputArgument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
@@ -145,7 +146,7 @@ class InputDefinition
*
* @param string|int $name The InputArgument name or position
*
- * @return bool true if the InputArgument object exists, false otherwise
+ * @return bool
*/
public function hasArgument($name)
{
@@ -157,7 +158,7 @@ class InputDefinition
/**
* Gets the array of InputArgument objects.
*
- * @return InputArgument[] An array of InputArgument objects
+ * @return InputArgument[]
*/
public function getArguments()
{
@@ -167,17 +168,17 @@ class InputDefinition
/**
* Returns the number of InputArguments.
*
- * @return int The number of InputArguments
+ * @return int
*/
public function getArgumentCount()
{
- return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
+ return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
- * @return int The number of required InputArguments
+ * @return int
*/
public function getArgumentRequiredCount()
{
@@ -185,9 +186,7 @@ class InputDefinition
}
/**
- * Gets the default values.
- *
- * @return array An array of default values
+ * @return array$title
-
-EOF;
- }
-
- $content = '';
- try {
- $count = \count($exception->getAllPrevious());
- $total = $count + 1;
- foreach ($exception->toArray() as $position => $e) {
- $ind = $count - $position + 1;
- $class = $this->formatClass($e['class']);
- $message = nl2br($this->escapeHtml($e['message']));
- $content .= sprintf(<<<'EOF'
-
-
\n
-
-EOF
- , $ind, $total, $class, $message);
- foreach ($e['trace'] as $trace) {
- $content .= '
-
- (%d/%d)
- %s
-
- \n";
- }
-
- $content .= "\n';
- if ($trace['function']) {
- $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args']));
- }
- if (isset($trace['file']) && isset($trace['line'])) {
- $content .= $this->formatPath($trace['file'], $trace['line']);
- }
- $content .= " $title
-