".
+ *
+ * Example ->error('Foo') would yield "error Foo".
+ *
+ * @return string[]
+ */
+ abstract public function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException \Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ if (method_exists($this, 'createPartialMock')) {
+ $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
+ } else {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ }
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+
+ $expected = array('warning DUMMY');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $closed = fopen('php://memory', 'r');
+ fclose($closed);
+
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ 'closed' => $closed,
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+
+ $expected = array('warning Crazy context data');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $logger = $this->getLogger();
+ $logger->warning('Random message', array('exception' => 'oops'));
+ $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+
+ $expected = array(
+ 'warning Random message',
+ 'critical Uncaught Exception!'
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php
new file mode 100644
index 0000000..1be3230
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php
@@ -0,0 +1,147 @@
+ $level,
+ 'message' => $message,
+ 'context' => $context,
+ ];
+
+ $this->recordsByLevel[$record['level']][] = $record;
+ $this->records[] = $record;
+ }
+
+ public function hasRecords($level)
+ {
+ return isset($this->recordsByLevel[$level]);
+ }
+
+ public function hasRecord($record, $level)
+ {
+ if (is_string($record)) {
+ $record = ['message' => $record];
+ }
+ return $this->hasRecordThatPasses(function ($rec) use ($record) {
+ if ($rec['message'] !== $record['message']) {
+ return false;
+ }
+ if (isset($record['context']) && $rec['context'] !== $record['context']) {
+ return false;
+ }
+ return true;
+ }, $level);
+ }
+
+ public function hasRecordThatContains($message, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($message) {
+ return strpos($rec['message'], $message) !== false;
+ }, $level);
+ }
+
+ public function hasRecordThatMatches($regex, $level)
+ {
+ return $this->hasRecordThatPasses(function ($rec) use ($regex) {
+ return preg_match($regex, $rec['message']) > 0;
+ }, $level);
+ }
+
+ public function hasRecordThatPasses(callable $predicate, $level)
+ {
+ if (!isset($this->recordsByLevel[$level])) {
+ return false;
+ }
+ foreach ($this->recordsByLevel[$level] as $i => $rec) {
+ if (call_user_func($predicate, $rec, $i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function __call($method, $args)
+ {
+ if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
+ $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
+ $level = strtolower($matches[2]);
+ if (method_exists($this, $genericMethod)) {
+ $args[] = $level;
+ return call_user_func_array([$this, $genericMethod], $args);
+ }
+ }
+ throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
+ }
+
+ public function reset()
+ {
+ $this->records = [];
+ $this->recordsByLevel = [];
+ }
+}
diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md
new file mode 100644
index 0000000..a9f20c4
--- /dev/null
+++ b/vendor/psr/log/README.md
@@ -0,0 +1,58 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Installation
+------------
+
+```bash
+composer require psr/log
+```
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ try {
+ $this->doSomethingElse();
+ } catch (Exception $exception) {
+ $this->logger->error('Oh no!', array('exception' => $exception));
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json
new file mode 100644
index 0000000..ca05695
--- /dev/null
+++ b/vendor/psr/log/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "homepage": "https://github.com/php-fig/log",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/cache-contracts/.gitignore b/vendor/symfony/cache-contracts/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/cache-contracts/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/cache-contracts/CHANGELOG.md b/vendor/symfony/cache-contracts/CHANGELOG.md
new file mode 100644
index 0000000..7932e26
--- /dev/null
+++ b/vendor/symfony/cache-contracts/CHANGELOG.md
@@ -0,0 +1,5 @@
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
diff --git a/vendor/symfony/cache-contracts/CacheInterface.php b/vendor/symfony/cache-contracts/CacheInterface.php
new file mode 100644
index 0000000..67e4dfd
--- /dev/null
+++ b/vendor/symfony/cache-contracts/CacheInterface.php
@@ -0,0 +1,57 @@
+
+ *
+ * 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;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Covers most simple to advanced caching needs.
+ *
+ * @author Nicolas Grekas
+ */
+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/vendor/symfony/cache-contracts/CacheTrait.php b/vendor/symfony/cache-contracts/CacheTrait.php
new file mode 100644
index 0000000..d340e06
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache-contracts/CallbackInterface.php b/vendor/symfony/cache-contracts/CallbackInterface.php
new file mode 100644
index 0000000..7dae2aa
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache-contracts/ItemInterface.php b/vendor/symfony/cache-contracts/ItemInterface.php
new file mode 100644
index 0000000..10c0488
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache-contracts/LICENSE b/vendor/symfony/cache-contracts/LICENSE
new file mode 100644
index 0000000..74cdc2d
--- /dev/null
+++ b/vendor/symfony/cache-contracts/LICENSE
@@ -0,0 +1,19 @@
+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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/cache-contracts/README.md b/vendor/symfony/cache-contracts/README.md
new file mode 100644
index 0000000..7085a69
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache-contracts/TagAwareCacheInterface.php b/vendor/symfony/cache-contracts/TagAwareCacheInterface.php
new file mode 100644
index 0000000..7c4cf11
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache-contracts/composer.json b/vendor/symfony/cache-contracts/composer.json
new file mode 100644
index 0000000..9f45e17
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Adapter/AbstractAdapter.php b/vendor/symfony/cache/Adapter/AbstractAdapter.php
new file mode 100644
index 0000000..0d1c671
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/AbstractAdapter.php
@@ -0,0 +1,208 @@
+
+ *
+ * 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 Psr\Log\LoggerInterface;
+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\CacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
+ /**
+ * @internal
+ */
+ protected const NS_SEPARATOR = ':';
+
+ private static $apcuSupported;
+ private static $phpFilesSupported;
+
+ 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));
+ }
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $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
+ );
+ self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind(
+ static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) {
+ $byLifetime = [];
+ $now = microtime(true);
+ $expiredIds = [];
+
+ foreach ($deferred as $key => $item) {
+ $key = (string) $key;
+ if (null === $item->expiry) {
+ $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;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * 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(string $namespace, int $defaultLifetime, string $version, string $directory, LoggerInterface $logger = null)
+ {
+ $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
+ if (null !== $logger) {
+ $opcache->setLogger($logger);
+ }
+
+ if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
+ return $opcache;
+ }
+
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
+ return $opcache;
+ }
+
+ $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version);
+ if (null !== $logger) {
+ $apcu->setLogger($logger);
+ }
+
+ return new ChainAdapter([$apcu, $opcache]);
+ }
+
+ public static function createConnection(string $dsn, array $options = [])
+ {
+ if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) {
+ return RedisAdapter::createConnection($dsn, $options);
+ }
+ 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}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ $ok = true;
+ $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime);
+ $retry = $this->deferred = [];
+
+ if ($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 {
+ $e = $this->doSave($values, $lifetime);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ if (\is_array($e) || 1 === \count($values)) {
+ foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+ $ok = false;
+ $v = $values[$id];
+ $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) {
+ $retry[$lifetime][] = $id;
+ }
+ }
+ }
+
+ // When bulk-save failed, retry each item individually
+ foreach ($retry as $lifetime => $ids) {
+ foreach ($ids as $id) {
+ try {
+ $v = $byLifetime[$lifetime][$id];
+ $e = $this->doSave([$id => $v], $lifetime);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ $ok = false;
+ $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;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
new file mode 100644
index 0000000..a384b16
--- /dev/null
+++ b/vendor/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
+ *
+ * @internal
+ */
+abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
+ private const TAGS_PREFIX = "\0tags\0";
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+ $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));
+ }
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->isTaggable = true;
+ // If structure does not match what we expect return item as is (no value and not a hit)
+ if (!\is_array($value) || !\array_key_exists('value', $value)) {
+ return $item;
+ }
+ $item->isHit = $isHit;
+ // Extract value, tags and meta data from the cache value
+ $item->value = $value['value'];
+ $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
+ if (isset($value['meta'])) {
+ // For compactness these values are packed, & expiry is offset to reduce size
+ $v = unpack('Ve/Nc', $value['meta']);
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind(
+ static function ($deferred, &$expiredIds, $getId, $tagPrefix, $defaultLifetime) {
+ $byLifetime = [];
+ $now = microtime(true);
+ $expiredIds = [];
+
+ foreach ($deferred as $key => $item) {
+ $key = (string) $key;
+ if (null === $item->expiry) {
+ $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
+ } elseif (!$item->expiry) {
+ $ttl = 0;
+ } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
+ $expiredIds[] = $getId($key);
+ continue;
+ }
+ // Store Value and Tags on the cache value
+ if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+ $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ } else {
+ $value = ['value' => $item->value, 'tags' => []];
+ }
+
+ if ($metadata) {
+ // For compactness, expiry and creation duration are packed, using magic numbers as separators
+ $value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
+ }
+
+ // Extract tag changes, these should be removed from values in doSave()
+ $value['tag-operations'] = ['add' => [], 'remove' => []];
+ $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
+ foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
+ $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
+ }
+ foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
+ $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
+ }
+
+ $byLifetime[$ttl][$getId($key)] = $value;
+ $item->metadata = $item->newMetadata;
+ }
+
+ return $byLifetime;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * 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
+ * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
+ * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
+ *
+ * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
+ */
+ abstract protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array;
+
+ /**
+ * Removes multiple items from the pool and their corresponding tags.
+ *
+ * @param array $ids An array of identifiers that should be removed from the pool
+ *
+ * @return bool
+ */
+ abstract protected function doDelete(array $ids);
+
+ /**
+ * Removes relations between tags and deleted items.
+ *
+ * @param array $tagData Array of tag => key identifiers that should be removed from the pool
+ */
+ abstract protected function doDeleteTagRelations(array $tagData): bool;
+
+ /**
+ * Invalidates cached items using tags.
+ *
+ * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
+ */
+ abstract protected function doInvalidate(array $tagIds): bool;
+
+ /**
+ * Delete items and yields the tags they were bound to.
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ foreach ($this->doFetch($ids) as $id => $value) {
+ yield $id => \is_array($value) && \is_array($value['tags'] ?? null) ? $value['tags'] : [];
+ }
+
+ $this->doDelete($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commit(): bool
+ {
+ $ok = true;
+ $byLifetime = (self::$mergeByLifetime)($this->deferred, $expiredIds, \Closure::fromCallable([$this, 'getId']), self::TAGS_PREFIX, $this->defaultLifetime);
+ $retry = $this->deferred = [];
+
+ if ($expiredIds) {
+ // Tags are not cleaned up in this case, however that is done on invalidateTags().
+ 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 {
+ $values = $this->extractTagData($values, $addTagData, $removeTagData);
+ $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ if (\is_array($e) || 1 === \count($values)) {
+ foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+ $ok = false;
+ $v = $values[$id];
+ $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) {
+ $retry[$lifetime][] = $id;
+ }
+ }
+ }
+
+ // When bulk-save failed, retry each item individually
+ foreach ($retry as $lifetime => $ids) {
+ foreach ($ids as $id) {
+ try {
+ $v = $byLifetime[$lifetime][$id];
+ $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
+ $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ $ok = false;
+ $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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteItems(array $keys): bool
+ {
+ if (!$keys) {
+ return true;
+ }
+
+ $ok = true;
+ $ids = [];
+ $tagData = [];
+
+ foreach ($keys as $key) {
+ $ids[$key] = $this->getId($key);
+ unset($this->deferred[$key]);
+ }
+
+ try {
+ foreach ($this->doDeleteYieldTags(array_values($ids)) as $id => $tags) {
+ foreach ($tags as $tag) {
+ $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
+ }
+ }
+ } catch (\Exception $e) {
+ $ok = false;
+ }
+
+ try {
+ if ((!$tagData || $this->doDeleteTagRelations($tagData)) && $ok) {
+ return true;
+ }
+ } catch (\Exception $e) {
+ }
+
+ // When bulk-delete failed, retry each item individually
+ foreach ($ids as $key => $id) {
+ try {
+ $e = null;
+ if ($this->doDelete([$id])) {
+ continue;
+ }
+ } catch (\Exception $e) {
+ }
+ $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ if (empty($tags)) {
+ return false;
+ }
+
+ $tagIds = [];
+ foreach (array_unique($tags) as $tag) {
+ $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
+ }
+
+ try {
+ if ($this->doInvalidate($tagIds)) {
+ return true;
+ }
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to invalidate tags: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ }
+
+ return false;
+ }
+
+ /**
+ * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
+ */
+ private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
+ {
+ $addTagData = $removeTagData = [];
+ foreach ($values as $id => $value) {
+ foreach ($value['tag-operations']['add'] as $tag => $tagId) {
+ $addTagData[$tagId][] = $id;
+ }
+
+ foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
+ $removeTagData[$tagId][] = $id;
+ }
+
+ unset($values[$id]['tag-operations']);
+ }
+
+ return $values;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/AdapterInterface.php b/vendor/symfony/cache/Adapter/AdapterInterface.php
new file mode 100644
index 0000000..f8dce86
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/AdapterInterface.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(CacheItem::class);
+
+/**
+ * Interface for adapters managing instances of Symfony's CacheItem.
+ *
+ * @author Kévin Dunglas
+ */
+interface AdapterInterface extends CacheItemPoolInterface
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @return CacheItem
+ */
+ public function getItem($key);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return \Traversable
+ */
+ public function getItems(array $keys = []);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '');
+}
diff --git a/vendor/symfony/cache/Adapter/ApcuAdapter.php b/vendor/symfony/cache/Adapter/ApcuAdapter.php
new file mode 100644
index 0000000..5fc8f62
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ApcuAdapter.php
@@ -0,0 +1,138 @@
+
+ *
+ * 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\CacheItem;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ApcuAdapter extends AbstractAdapter
+{
+ private $marshaller;
+
+ /**
+ * @throws CacheException if APCu is not enabled
+ */
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null, MarshallerInterface $marshaller = null)
+ {
+ 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/vendor/symfony/cache/Adapter/ArrayAdapter.php b/vendor/symfony/cache/Adapter/ArrayAdapter.php
new file mode 100644
index 0000000..bd5ec9e
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ArrayAdapter.php
@@ -0,0 +1,404 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+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, CacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use LoggerAwareTrait;
+
+ private $storeSerialized;
+ private $values = [];
+ private $expiries = [];
+ private $defaultLifetime;
+ private $maxLifetime;
+ private $maxItems;
+
+ private static $createCacheItem;
+
+ /**
+ * @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
+ */
+ 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->maxLifetime = $maxLifetime;
+ $this->maxItems = $maxItems;
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->isHit = $isHit;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@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)
+ {
+ if (!$isHit = $this->hasItem($key)) {
+ $value = null;
+
+ 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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ \assert(self::validateKeys($keys));
+
+ 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)
+ {
+ foreach ($keys as $key) {
+ $this->deleteItem($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $item = (array) $item;
+ $key = $item["\0*\0key"];
+ $value = $item["\0*\0value"];
+ $expiry = $item["\0*\0expiry"];
+
+ $now = microtime(true);
+
+ if (null !== $expiry) {
+ if (!$expiry) {
+ $expiry = \PHP_INT_MAX;
+ } elseif ($expiry <= $now) {
+ $this->deleteItem($key);
+
+ return true;
+ }
+ }
+ if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
+ return false;
+ }
+ if (null === $expiry && 0 < $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] = $expiry ?? \PHP_INT_MAX;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ return $this->save($item);
+ }
+
+ /**
+ * {@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/vendor/symfony/cache/Adapter/ChainAdapter.php b/vendor/symfony/cache/Adapter/ChainAdapter.php
new file mode 100644
index 0000000..5680697
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ChainAdapter.php
@@ -0,0 +1,333 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+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.
+ *
+ * Cached items are fetched from the first adapter having them in its data store.
+ * They are saved and deleted in all adapters at once.
+ *
+ * @author Kévin Dunglas
+ */
+class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ContractsTrait;
+
+ private $adapters = [];
+ private $adapterCount;
+ private $defaultLifetime;
+
+ private static $syncItem;
+
+ /**
+ * @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items
+ * @param int $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones
+ */
+ public function __construct(array $adapters, int $defaultLifetime = 0)
+ {
+ if (!$adapters) {
+ throw new InvalidArgumentException('At least one adapter must be specified.');
+ }
+
+ foreach ($adapters as $adapter) {
+ if (!$adapter instanceof CacheItemPoolInterface) {
+ throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class));
+ }
+ if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
+ continue; // skip putting APCu in the chain when the backend is disabled
+ }
+
+ if ($adapter instanceof AdapterInterface) {
+ $this->adapters[] = $adapter;
+ } else {
+ $this->adapters[] = new ProxyAdapter($adapter);
+ }
+ }
+ $this->adapterCount = \count($this->adapters);
+ $this->defaultLifetime = $defaultLifetime;
+
+ self::$syncItem ?? self::$syncItem = \Closure::bind(
+ static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) {
+ $sourceItem->isTaggable = false;
+ $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
+ unset($sourceMetadata[CacheItem::METADATA_TAGS]);
+
+ $item->value = $sourceItem->value;
+ $item->isHit = $sourceItem->isHit;
+ $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
+
+ if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) {
+ $item->expiresAt(\DateTime::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY])));
+ } elseif (0 < $defaultLifetime) {
+ $item->expiresAfter($defaultLifetime);
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $lastItem = null;
+ $i = 0;
+ $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) {
+ $adapter = $this->adapters[$i];
+ if (isset($this->adapters[++$i])) {
+ $callback = $wrap;
+ $beta = \INF === $beta ? \INF : 0;
+ }
+ if ($adapter instanceof CacheInterface) {
+ $value = $adapter->get($key, $callback, $beta, $metadata);
+ } else {
+ $value = $this->doGet($adapter, $key, $callback, $beta, $metadata);
+ }
+ if (null !== $item) {
+ (self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata);
+ }
+
+ return $value;
+ };
+
+ return $wrap();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $syncItem = self::$syncItem;
+ $misses = [];
+
+ foreach ($this->adapters as $i => $adapter) {
+ $item = $adapter->getItem($key);
+
+ if ($item->isHit()) {
+ while (0 <= --$i) {
+ $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime));
+ }
+
+ return $item;
+ }
+
+ $misses[$i] = $item;
+ }
+
+ return $item;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ return $this->generateItems($this->adapters[0]->getItems($keys), 0);
+ }
+
+ private function generateItems(iterable $items, int $adapterIndex): \Generator
+ {
+ $missing = [];
+ $misses = [];
+ $nextAdapterIndex = $adapterIndex + 1;
+ $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null;
+
+ foreach ($items as $k => $item) {
+ if (!$nextAdapter || $item->isHit()) {
+ yield $k => $item;
+ } else {
+ $missing[] = $k;
+ $misses[$k] = $item;
+ }
+ }
+
+ if ($missing) {
+ $syncItem = self::$syncItem;
+ $adapter = $this->adapters[$adapterIndex];
+ $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
+
+ foreach ($items as $k => $item) {
+ if ($item->isHit()) {
+ $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime));
+ }
+
+ yield $k => $item;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ foreach ($this->adapters as $adapter) {
+ if ($adapter->hasItem($key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '')
+ {
+ $cleared = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ if ($this->adapters[$i] instanceof AdapterInterface) {
+ $cleared = $this->adapters[$i]->clear($prefix) && $cleared;
+ } else {
+ $cleared = $this->adapters[$i]->clear() && $cleared;
+ }
+ }
+
+ return $cleared;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ $deleted = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $deleted = $this->adapters[$i]->deleteItem($key) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $deleted = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ $saved = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $saved = $this->adapters[$i]->save($item) && $saved;
+ }
+
+ return $saved;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ $saved = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $saved = $this->adapters[$i]->saveDeferred($item) && $saved;
+ }
+
+ return $saved;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ $committed = true;
+ $i = $this->adapterCount;
+
+ while ($i--) {
+ $committed = $this->adapters[$i]->commit() && $committed;
+ }
+
+ return $committed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ $pruned = true;
+
+ foreach ($this->adapters as $adapter) {
+ if ($adapter instanceof PruneableInterface) {
+ $pruned = $adapter->prune() && $pruned;
+ }
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ foreach ($this->adapters as $adapter) {
+ if ($adapter instanceof ResetInterface) {
+ $adapter->reset();
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php
new file mode 100644
index 0000000..36d5249
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/CouchbaseBucketAdapter.php
@@ -0,0 +1,252 @@
+
+ *
+ * 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\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Antonio Jose Cerezo Aranda
+ */
+class CouchbaseBucketAdapter extends AbstractAdapter
+{
+ private const THIRTY_DAYS_IN_SECONDS = 2592000;
+ private const MAX_KEY_LENGTH = 250;
+ private const KEY_NOT_FOUND = 13;
+ private const VALID_DSN_OPTIONS = [
+ 'operationTimeout',
+ 'configTimeout',
+ 'configNodeTimeout',
+ 'n1qlTimeout',
+ 'httpTimeout',
+ 'configDelay',
+ 'htconfigIdleTimeout',
+ 'durabilityInterval',
+ 'durabilityTimeout',
+ ];
+
+ private $bucket;
+ private $marshaller;
+
+ public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ if (!static::isSupported()) {
+ throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
+ }
+
+ $this->maxIdLength = static::MAX_KEY_LENGTH;
+
+ $this->bucket = $bucket;
+
+ parent::__construct($namespace, $defaultLifetime);
+ $this->enableVersioning();
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * @param array|string $servers
+ */
+ public static function createConnection($servers, array $options = []): \CouchbaseBucket
+ {
+ if (\is_string($servers)) {
+ $servers = [$servers];
+ } elseif (!\is_array($servers)) {
+ throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($servers)));
+ }
+
+ if (!static::isSupported()) {
+ throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
+ }
+
+ set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
+
+ $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?'
+ .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\?]+))(?:\?(?.*))?$/i';
+
+ $newServers = [];
+ $protocol = 'couchbase';
+ try {
+ $options = self::initOptions($options);
+ $username = $options['username'];
+ $password = $options['password'];
+
+ foreach ($servers as $dsn) {
+ if (0 !== strpos($dsn, 'couchbase:')) {
+ throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $dsn));
+ }
+
+ preg_match($dsnPattern, $dsn, $matches);
+
+ $username = $matches['username'] ?: $username;
+ $password = $matches['password'] ?: $password;
+ $protocol = $matches['protocol'] ?: $protocol;
+
+ if (isset($matches['options'])) {
+ $optionsInDsn = self::getOptions($matches['options']);
+
+ foreach ($optionsInDsn as $parameter => $value) {
+ $options[$parameter] = $value;
+ }
+ }
+
+ $newServers[] = $matches['host'];
+ }
+
+ $connectionString = $protocol.'://'.implode(',', $newServers);
+
+ $client = new \CouchbaseCluster($connectionString);
+ $client->authenticateAs($username, $password);
+
+ $bucket = $client->openBucket($matches['bucketName']);
+
+ unset($options['username'], $options['password']);
+ foreach ($options as $option => $value) {
+ if (!empty($value)) {
+ $bucket->$option = $value;
+ }
+ }
+
+ return $bucket;
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ public static function isSupported(): bool
+ {
+ return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<');
+ }
+
+ private static function getOptions(string $options): array
+ {
+ $results = [];
+ $optionsInArray = explode('&', $options);
+
+ foreach ($optionsInArray as $option) {
+ [$key, $value] = explode('=', $option);
+
+ if (\in_array($key, static::VALID_DSN_OPTIONS, true)) {
+ $results[$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ private static function initOptions(array $options): array
+ {
+ $options['username'] = $options['username'] ?? '';
+ $options['password'] = $options['password'] ?? '';
+ $options['operationTimeout'] = $options['operationTimeout'] ?? 0;
+ $options['configTimeout'] = $options['configTimeout'] ?? 0;
+ $options['configNodeTimeout'] = $options['configNodeTimeout'] ?? 0;
+ $options['n1qlTimeout'] = $options['n1qlTimeout'] ?? 0;
+ $options['httpTimeout'] = $options['httpTimeout'] ?? 0;
+ $options['configDelay'] = $options['configDelay'] ?? 0;
+ $options['htconfigIdleTimeout'] = $options['htconfigIdleTimeout'] ?? 0;
+ $options['durabilityInterval'] = $options['durabilityInterval'] ?? 0;
+ $options['durabilityTimeout'] = $options['durabilityTimeout'] ?? 0;
+
+ return $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $resultsCouchbase = $this->bucket->get($ids);
+
+ $results = [];
+ foreach ($resultsCouchbase as $key => $value) {
+ if (null !== $value->error) {
+ continue;
+ }
+ $results[$key] = $this->marshaller->unmarshall($value->value);
+ }
+
+ return $results;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id): bool
+ {
+ return false !== $this->bucket->get($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace): bool
+ {
+ if ('' === $namespace) {
+ $this->bucket->manager()->flush();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids): bool
+ {
+ $results = $this->bucket->remove(array_values($ids));
+
+ foreach ($results as $key => $result) {
+ if (null !== $result->error && static::KEY_NOT_FOUND !== $result->error->getCode()) {
+ continue;
+ }
+ unset($results[$key]);
+ }
+
+ return 0 === \count($results);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ $lifetime = $this->normalizeExpiry($lifetime);
+
+ $ko = [];
+ foreach ($values as $key => $value) {
+ $result = $this->bucket->upsert($key, $value, ['expiry' => $lifetime]);
+
+ if (null !== $result->error) {
+ $ko[$key] = $result;
+ }
+ }
+
+ return [] === $ko ? true : $ko;
+ }
+
+ private function normalizeExpiry(int $expiry): int
+ {
+ if ($expiry && $expiry > static::THIRTY_DAYS_IN_SECONDS) {
+ $expiry += time();
+ }
+
+ return $expiry;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php
new file mode 100644
index 0000000..79f6485
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/CouchbaseCollectionAdapter.php
@@ -0,0 +1,222 @@
+
+ *
+ * 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 Couchbase\Bucket;
+use Couchbase\Cluster;
+use Couchbase\ClusterOptions;
+use Couchbase\Collection;
+use Couchbase\DocumentNotFoundException;
+use Couchbase\UpsertOptions;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Antonio Jose Cerezo Aranda
+ */
+class CouchbaseCollectionAdapter extends AbstractAdapter
+{
+ private const MAX_KEY_LENGTH = 250;
+
+ /** @var Collection */
+ private $connection;
+ private $marshaller;
+
+ public function __construct(Collection $connection, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ if (!static::isSupported()) {
+ throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.');
+ }
+
+ $this->maxIdLength = static::MAX_KEY_LENGTH;
+
+ $this->connection = $connection;
+
+ parent::__construct($namespace, $defaultLifetime);
+ $this->enableVersioning();
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * @param array|string $dsn
+ *
+ * @return Bucket|Collection
+ */
+ public static function createConnection($dsn, array $options = [])
+ {
+ if (\is_string($dsn)) {
+ $dsn = [$dsn];
+ } elseif (!\is_array($dsn)) {
+ throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array or string, "%s" given.', __METHOD__, get_debug_type($dsn)));
+ }
+
+ if (!static::isSupported()) {
+ throw new CacheException('Couchbase >= 3.0.0 < 4.0.0 is required.');
+ }
+
+ set_error_handler(function ($type, $msg, $file, $line): bool { throw new \ErrorException($msg, 0, $type, $file, $line); });
+
+ $dsnPattern = '/^(?couchbase(?:s)?)\:\/\/(?:(?[^\:]+)\:(?[^\@]{6,})@)?'
+ .'(?[^\:]+(?:\:\d+)?)(?:\/(?[^\/\?]+))(?:(?:\/(?[^\/]+))'
+ .'(?:\/(?[^\/\?]+)))?(?:\/)?(?:\?(?.*))?$/i';
+
+ $newServers = [];
+ $protocol = 'couchbase';
+ try {
+ $username = $options['username'] ?? '';
+ $password = $options['password'] ?? '';
+
+ foreach ($dsn as $server) {
+ if (0 !== strpos($server, 'couchbase:')) {
+ throw new InvalidArgumentException(sprintf('Invalid Couchbase DSN: "%s" does not start with "couchbase:".', $server));
+ }
+
+ preg_match($dsnPattern, $server, $matches);
+
+ $username = $matches['username'] ?: $username;
+ $password = $matches['password'] ?: $password;
+ $protocol = $matches['protocol'] ?: $protocol;
+
+ if (isset($matches['options'])) {
+ $optionsInDsn = self::getOptions($matches['options']);
+
+ foreach ($optionsInDsn as $parameter => $value) {
+ $options[$parameter] = $value;
+ }
+ }
+
+ $newServers[] = $matches['host'];
+ }
+
+ $option = isset($matches['options']) ? '?'.$matches['options'] : '';
+ $connectionString = $protocol.'://'.implode(',', $newServers).$option;
+
+ $clusterOptions = new ClusterOptions();
+ $clusterOptions->credentials($username, $password);
+
+ $client = new Cluster($connectionString, $clusterOptions);
+
+ $bucket = $client->bucket($matches['bucketName']);
+ $collection = $bucket->defaultCollection();
+ if (!empty($matches['scopeName'])) {
+ $scope = $bucket->scope($matches['scopeName']);
+ $collection = $scope->collection($matches['collectionName']);
+ }
+
+ return $collection;
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ public static function isSupported(): bool
+ {
+ return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '3.0.5', '>=') && version_compare(phpversion('couchbase'), '4.0', '<');
+ }
+
+ private static function getOptions(string $options): array
+ {
+ $results = [];
+ $optionsInArray = explode('&', $options);
+
+ foreach ($optionsInArray as $option) {
+ [$key, $value] = explode('=', $option);
+
+ $results[$key] = $value;
+ }
+
+ return $results;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids): array
+ {
+ $results = [];
+ foreach ($ids as $id) {
+ try {
+ $resultCouchbase = $this->connection->get($id);
+ } catch (DocumentNotFoundException $exception) {
+ continue;
+ }
+
+ $content = $resultCouchbase->value ?? $resultCouchbase->content();
+
+ $results[$id] = $this->marshaller->unmarshall($content);
+ }
+
+ return $results;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id): bool
+ {
+ return $this->connection->exists($id)->exists();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace): bool
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids): bool
+ {
+ $idsErrors = [];
+ foreach ($ids as $id) {
+ try {
+ $result = $this->connection->remove($id);
+
+ if (null === $result->mutationToken()) {
+ $idsErrors[] = $id;
+ }
+ } catch (DocumentNotFoundException $exception) {
+ }
+ }
+
+ return 0 === \count($idsErrors);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ $upsertOptions = new UpsertOptions();
+ $upsertOptions->expiry($lifetime);
+
+ $ko = [];
+ foreach ($values as $key => $value) {
+ try {
+ $this->connection->upsert($key, $value, $upsertOptions);
+ } catch (\Exception $exception) {
+ $ko[$key] = '';
+ }
+ }
+
+ return [] === $ko ? true : $ko;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/DoctrineAdapter.php b/vendor/symfony/cache/Adapter/DoctrineAdapter.php
new file mode 100644
index 0000000..efa30c8
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/DoctrineAdapter.php
@@ -0,0 +1,110 @@
+
+ *
+ * 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\Common\Cache\CacheProvider;
+use Doctrine\Common\Cache\Psr6\CacheAdapter;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @deprecated Since Symfony 5.4, use Doctrine\Common\Cache\Psr6\CacheAdapter instead
+ */
+class DoctrineAdapter extends AbstractAdapter
+{
+ private $provider;
+
+ 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/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php b/vendor/symfony/cache/Adapter/DoctrineDbalAdapter.php
new file mode 100644
index 0000000..73f0ea6
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Adapter/FilesystemAdapter.php b/vendor/symfony/cache/Adapter/FilesystemAdapter.php
new file mode 100644
index 0000000..7185dd4
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/FilesystemAdapter.php
@@ -0,0 +1,29 @@
+
+ *
+ * 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\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+
+class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
+{
+ use FilesystemTrait;
+
+ 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/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
new file mode 100644
index 0000000..afde843
--- /dev/null
+++ b/vendor/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 FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
+{
+ use FilesystemTrait {
+ doClear as private doClearCache;
+ doSave as private doSaveCache;
+ }
+
+ /**
+ * Folder used for tag symlinks.
+ */
+ private const TAG_FOLDER = 'tags';
+
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = new TagAwareMarshaller($marshaller);
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ $ok = $this->doClearCache($namespace);
+
+ if ('' !== $namespace) {
+ return $ok;
+ }
+
+ set_error_handler(static function () {});
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ try {
+ foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) {
+ if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) {
+ $dir = $renamed.\DIRECTORY_SEPARATOR;
+ } else {
+ $dir .= \DIRECTORY_SEPARATOR;
+ $renamed = null;
+ }
+
+ for ($i = 0; $i < 38; ++$i) {
+ if (!is_dir($dir.$chars[$i])) {
+ continue;
+ }
+ for ($j = 0; $j < 38; ++$j) {
+ if (!is_dir($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
+ continue;
+ }
+ foreach (scandir($d, \SCANDIR_SORT_NONE) ?: [] as $link) {
+ if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) {
+ unlink($d.\DIRECTORY_SEPARATOR.$link);
+ }
+ }
+ null === $renamed ?: rmdir($d);
+ }
+ null === $renamed ?: rmdir($dir.$chars[$i]);
+ }
+ null === $renamed ?: rmdir($renamed);
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime, array $addTagData = [], array $removeTagData = []): array
+ {
+ $failed = $this->doSaveCache($values, $lifetime);
+
+ // Add Tags as symlinks
+ foreach ($addTagData as $tagId => $ids) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($ids as $id) {
+ if ($failed && \in_array($id, $failed, true)) {
+ continue;
+ }
+
+ $file = $this->getFile($id);
+
+ if (!@symlink($file, $tagLink = $this->getFile($id, true, $tagFolder)) && !is_link($tagLink)) {
+ @unlink($file);
+ $failed[] = $id;
+ }
+ }
+ }
+
+ // Unlink removed Tags
+ foreach ($removeTagData as $tagId => $ids) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($ids as $id) {
+ if ($failed && \in_array($id, $failed, true)) {
+ continue;
+ }
+
+ @unlink($this->getFile($id, false, $tagFolder));
+ }
+ }
+
+ return $failed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ if (!is_file($file) || !$h = @fopen($file, 'r')) {
+ continue;
+ }
+
+ if ((\PHP_VERSION_ID >= 70300 || '\\' !== \DIRECTORY_SEPARATOR) && !@unlink($file)) {
+ fclose($h);
+ continue;
+ }
+
+ $meta = explode("\n", fread($h, 4096), 3)[2] ?? '';
+
+ // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
+ if (13 < \strlen($meta) && "\x9D" === $meta[0] && "\0" === $meta[5] && "\x5F" === $meta[9]) {
+ $meta[9] = "\0";
+ $tagLen = unpack('Nlen', $meta, 9)['len'];
+ $meta = substr($meta, 13, $tagLen);
+
+ if (0 < $tagLen -= \strlen($meta)) {
+ $meta .= fread($h, $tagLen);
+ }
+
+ try {
+ yield $id => '' === $meta ? [] : $this->marshaller->unmarshall($meta);
+ } catch (\Exception $e) {
+ yield $id => [];
+ }
+ }
+
+ fclose($h);
+
+ if (\PHP_VERSION_ID < 70300 && '\\' === \DIRECTORY_SEPARATOR) {
+ @unlink($file);
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteTagRelations(array $tagData): bool
+ {
+ foreach ($tagData as $tagId => $idList) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($idList as $id) {
+ @unlink($this->getFile($id, false, $tagFolder));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ foreach ($tagIds as $tagId) {
+ if (!is_dir($tagFolder = $this->getTagFolder($tagId))) {
+ continue;
+ }
+
+ set_error_handler(static function () {});
+
+ try {
+ if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) {
+ $tagFolder = $renamed.\DIRECTORY_SEPARATOR;
+ } else {
+ $renamed = null;
+ }
+
+ foreach ($this->scanHashDir($tagFolder) as $itemLink) {
+ unlink(realpath($itemLink) ?: $itemLink);
+ unlink($itemLink);
+ }
+
+ if (null === $renamed) {
+ continue;
+ }
+
+ $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+
+ for ($i = 0; $i < 38; ++$i) {
+ for ($j = 0; $j < 38; ++$j) {
+ rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
+ }
+ rmdir($tagFolder.$chars[$i]);
+ }
+ rmdir($renamed);
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ return true;
+ }
+
+ private function getTagFolder(string $tagId): string
+ {
+ return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/MemcachedAdapter.php b/vendor/symfony/cache/Adapter/MemcachedAdapter.php
new file mode 100644
index 0000000..5c2933f
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/MemcachedAdapter.php
@@ -0,0 +1,353 @@
+
+ *
+ * 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\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Rob Frawley 2nd
+ * @author Nicolas Grekas
+ */
+class MemcachedAdapter extends AbstractAdapter
+{
+ /**
+ * 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:
+ * - the Memcached::OPT_BINARY_PROTOCOL must be enabled
+ * (that's the default when using MemcachedAdapter::createConnection());
+ * - tags eviction by Memcached's LRU algorithm will break by-tags invalidation;
+ * your Memcached memory should be large enough to never trigger LRU.
+ *
+ * Using a MemcachedAdapter as a pure items store is fine.
+ */
+ public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ 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/vendor/symfony/cache/Adapter/NullAdapter.php b/vendor/symfony/cache/Adapter/NullAdapter.php
new file mode 100644
index 0000000..15f7f8c
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/NullAdapter.php
@@ -0,0 +1,152 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Titouan Galopin
+ */
+class NullAdapter implements AdapterInterface, CacheInterface
+{
+ private static $createCacheItem;
+
+ public function __construct()
+ {
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->isHit = false;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ $save = true;
+
+ return $callback((self::$createCacheItem)($key), $save);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ return (self::$createCacheItem)($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ return $this->generateItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '')
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(string $key): bool
+ {
+ return $this->deleteItem($key);
+ }
+
+ private function generateItems(array $keys): \Generator
+ {
+ $f = self::$createCacheItem;
+
+ foreach ($keys as $key) {
+ yield $key => $f($key);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/ParameterNormalizer.php b/vendor/symfony/cache/Adapter/ParameterNormalizer.php
new file mode 100644
index 0000000..e33ae9f
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ParameterNormalizer.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+/**
+ * @author Lars Strojny
+ */
+final class ParameterNormalizer
+{
+ public static function normalizeDuration(string $duration): int
+ {
+ if (is_numeric($duration)) {
+ return $duration;
+ }
+
+ if (false !== $time = strtotime($duration, 0)) {
+ return $time;
+ }
+
+ try {
+ return \DateTime::createFromFormat('U', 0)->add(new \DateInterval($duration))->getTimestamp();
+ } catch (\Exception $e) {
+ throw new \InvalidArgumentException(sprintf('Cannot parse date interval "%s".', $duration), 0, $e);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/PdoAdapter.php b/vendor/symfony/cache/Adapter/PdoAdapter.php
new file mode 100644
index 0000000..5d10724
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/PdoAdapter.php
@@ -0,0 +1,583 @@
+
+ *
+ * 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\Schema\Schema;
+use Psr\Cache\CacheItemInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+
+class PdoAdapter extends AbstractAdapter implements PruneableInterface
+{
+ protected $maxIdLength = 255;
+
+ private $marshaller;
+ 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 $dbalAdapter;
+
+ /**
+ * You can either pass an existing database connection as PDO instance 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|string $connOrDsn
+ *
+ * @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, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
+ {
+ if ($connOrDsn instanceof Connection || (\is_string($connOrDsn) && str_contains($connOrDsn, '://'))) {
+ trigger_deprecation('symfony/cache', '5.4', 'Usage of a DBAL Connection with "%s" is deprecated and will be removed in symfony 6.0. Use "%s" instead.', __CLASS__, DoctrineDbalAdapter::class);
+ $this->dbalAdapter = new DoctrineDbalAdapter($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
+
+ return;
+ }
+
+ 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 (\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__, 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->username = $options['db_username'] ?? $this->username;
+ $this->password = $options['db_password'] ?? $this->password;
+ $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions;
+ $this->namespace = $namespace;
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+
+ parent::__construct($namespace, $defaultLifetime);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getItem($key)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->getItem($key);
+ }
+
+ return parent::getItem($key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->getItems($keys);
+ }
+
+ return parent::getItems($keys);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function hasItem($key)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->hasItem($key);
+ }
+
+ return parent::hasItem($key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function deleteItem($key)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->deleteItem($key);
+ }
+
+ return parent::deleteItem($key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function deleteItems(array $keys)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->deleteItems($keys);
+ }
+
+ return parent::deleteItems($keys);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function clear(string $prefix = '')
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->clear($prefix);
+ }
+
+ return parent::clear($prefix);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->get($key, $callback, $beta, $metadata);
+ }
+
+ return parent::get($key, $callback, $beta, $metadata);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function delete(string $key): bool
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->delete($key);
+ }
+
+ return parent::delete($key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->save($item);
+ }
+
+ return parent::save($item);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->saveDeferred($item);
+ }
+
+ return parent::saveDeferred($item);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function setLogger(LoggerInterface $logger): void
+ {
+ if (isset($this->dbalAdapter)) {
+ $this->dbalAdapter->setLogger($logger);
+
+ return;
+ }
+
+ parent::setLogger($logger);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function commit()
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->commit();
+ }
+
+ return parent::commit();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function reset()
+ {
+ if (isset($this->dbalAdapter)) {
+ $this->dbalAdapter->reset();
+
+ return;
+ }
+
+ parent::reset();
+ }
+
+ /**
+ * 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 \DomainException When an unsupported PDO driver is used
+ */
+ public function createTable()
+ {
+ if (isset($this->dbalAdapter)) {
+ $this->dbalAdapter->createTable();
+
+ return;
+ }
+
+ // connect if we are not yet
+ $conn = $this->getConnection();
+
+ 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 utf8mb4_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));
+ }
+
+ $conn->exec($sql);
+ }
+
+ /**
+ * Adds the Table to the Schema if the adapter uses this Connection.
+ *
+ * @deprecated since symfony/cache 5.4 use DoctrineDbalAdapter instead
+ */
+ public function configureSchema(Schema $schema, Connection $forConnection): void
+ {
+ if (isset($this->dbalAdapter)) {
+ $this->dbalAdapter->configureSchema($schema, $forConnection);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ if (isset($this->dbalAdapter)) {
+ return $this->dbalAdapter->prune();
+ }
+
+ $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
+
+ if ('' !== $this->namespace) {
+ $deleteSql .= " AND $this->idCol LIKE :namespace";
+ }
+
+ $connection = $this->getConnection();
+
+ try {
+ $delete = $connection->prepare($deleteSql);
+ } catch (\PDOException $e) {
+ return true;
+ }
+ $delete->bindValue(':time', time(), \PDO::PARAM_INT);
+
+ if ('' !== $this->namespace) {
+ $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
+ }
+ try {
+ return $delete->execute();
+ } catch (\PDOException $e) {
+ return true;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $connection = $this->getConnection();
+
+ $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 = $connection->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] => $this->marshaller->unmarshall(\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 = $connection->prepare($sql);
+ $stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
+ foreach ($expired as $id) {
+ $stmt->bindValue(++$i, $id);
+ }
+ $stmt->execute();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ $connection = $this->getConnection();
+
+ $sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
+ $stmt = $connection->prepare($sql);
+
+ $stmt->bindValue(':id', $id);
+ $stmt->bindValue(':time', time(), \PDO::PARAM_INT);
+ $stmt->execute();
+
+ return (bool) $stmt->fetchColumn();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $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%'";
+ }
+
+ try {
+ $conn->exec($sql);
+ } catch (\PDOException $e) {
+ }
+
+ 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)";
+ try {
+ $stmt = $this->getConnection()->prepare($sql);
+ $stmt->execute(array_values($ids));
+ } catch (\PDOException $e) {
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ 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;
+ try {
+ $stmt = $conn->prepare($sql);
+ } catch (\PDOException $e) {
+ if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt = $conn->prepare($sql);
+ }
+
+ // $id and $data are defined later in the loop. Binding is done by reference, values are read on execution.
+ 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 ($values as $id => $data) {
+ try {
+ $stmt->execute();
+ } catch (\PDOException $e) {
+ if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
+ $this->createTable();
+ }
+ $stmt->execute();
+ }
+ if (null === $driver && !$stmt->rowCount()) {
+ try {
+ $insertStmt->execute();
+ } catch (\PDOException $e) {
+ // A concurrent write won, let it be
+ }
+ }
+ }
+
+ return $failed;
+ }
+
+ private function getConnection(): \PDO
+ {
+ 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) {
+ $this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
+ }
+
+ return $this->conn;
+ }
+
+ private function getServerVersion(): string
+ {
+ if (null === $this->serverVersion) {
+ $this->serverVersion = $this->conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
+ }
+
+ return $this->serverVersion;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/PhpArrayAdapter.php b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php
new file mode 100644
index 0000000..7e9b6b3
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/PhpArrayAdapter.php
@@ -0,0 +1,435 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+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\Component\Cache\Traits\ProxyTrait;
+use Symfony\Component\VarExporter\VarExporter;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * 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
+ * @author Nicolas Grekas
+ */
+class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ContractsTrait;
+ use ProxyTrait;
+
+ 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(string $file, AdapterInterface $fallbackPool)
+ {
+ $this->file = $file;
+ $this->pool = $fallbackPool;
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->isHit = $isHit;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * 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(string $file, CacheItemPoolInterface $fallbackPool)
+ {
+ if (!$fallbackPool instanceof AdapterInterface) {
+ $fallbackPool = new ProxyAdapter($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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ if (!\is_string($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->keys[$key])) {
+ return $this->pool->getItem($key);
+ }
+
+ $value = $this->values[$this->keys[$key]];
+ $isHit = true;
+
+ if ('N;' === $value) {
+ $value = null;
+ } elseif ($value instanceof \Closure) {
+ try {
+ $value = $value();
+ } catch (\Throwable $e) {
+ $value = null;
+ $isHit = false;
+ }
+ }
+
+ return (self::$createCacheItem)($key, $value, $isHit);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ foreach ($keys as $key) {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
+ }
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return $this->generateItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ if (!\is_string($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->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.', get_debug_type($key)));
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $deleted = true;
+ $fallbackKeys = [];
+
+ foreach ($keys as $key) {
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
+ }
+
+ if (isset($this->keys[$key])) {
+ $deleted = false;
+ } else {
+ $fallbackKeys[] = $key;
+ }
+ }
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ if ($fallbackKeys) {
+ $deleted = $this->pool->deleteItems($fallbackKeys) && $deleted;
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (null === $this->values) {
+ $this->initialize();
+ }
+
+ return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return $this->pool->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '')
+ {
+ $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->keys[$key])) {
+ $value = $this->values[$this->keys[$key]];
+
+ if ('N;' === $value) {
+ yield $key => $f($key, null, true);
+ } elseif ($value instanceof \Closure) {
+ try {
+ yield $key => $f($key, $value(), true);
+ } catch (\Throwable $e) {
+ yield $key => $f($key, null, false);
+ }
+ } else {
+ yield $key => $f($key, $value, true);
+ }
+ } else {
+ $fallbackKeys[] = $key;
+ }
+ }
+
+ if ($fallbackKeys) {
+ yield from $this->pool->getItems($fallbackKeys);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/PhpFilesAdapter.php b/vendor/symfony/cache/Adapter/PhpFilesAdapter.php
new file mode 100644
index 0000000..f5766c3
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/PhpFilesAdapter.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 Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemCommonTrait;
+use Symfony\Component\VarExporter\VarExporter;
+
+/**
+ * @author Piotr Stankowski
+ * @author Nicolas Grekas
+ * @author Rob Frawley 2nd
+ */
+class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
+{
+ use FilesystemCommonTrait {
+ doClear as private doCommonClear;
+ doDelete as private doCommonDelete;
+ }
+
+ private $includeHandler;
+ private $appendOnly;
+ private $values = [];
+ private $files = [];
+
+ private static $startTime;
+ private static $valuesCache = [];
+
+ /**
+ * @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
+ * Doing so is encouraged because it fits perfectly OPcache's memory model.
+ *
+ * @throws CacheException if OPcache is not enabled
+ */
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
+ {
+ $this->appendOnly = $appendOnly;
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ $this->includeHandler = static function ($type, $msg, $file, $line) {
+ throw new \ErrorException($msg, 0, $type, $file, $line);
+ };
+ }
+
+ public static function isSupported()
+ {
+ self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
+
+ return \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN));
+ }
+
+ /**
+ * @return bool
+ */
+ public function prune()
+ {
+ $time = time();
+ $pruned = true;
+ $getExpiry = true;
+
+ set_error_handler($this->includeHandler);
+ try {
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ try {
+ if (\is_array($expiresAt = include $file)) {
+ $expiresAt = $expiresAt[0];
+ }
+ } catch (\ErrorException $e) {
+ $expiresAt = $time;
+ }
+
+ if ($time >= $expiresAt) {
+ $pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
+ }
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ if ($this->appendOnly) {
+ $now = 0;
+ $missingIds = [];
+ } else {
+ $now = time();
+ $missingIds = $ids;
+ $ids = [];
+ }
+ $values = [];
+
+ begin:
+ $getExpiry = false;
+
+ foreach ($ids as $id) {
+ if (null === $value = $this->values[$id] ?? null) {
+ $missingIds[] = $id;
+ } elseif ('N;' === $value) {
+ $values[$id] = null;
+ } elseif (!\is_object($value)) {
+ $values[$id] = $value;
+ } elseif (!$value instanceof LazyValue) {
+ $values[$id] = $value();
+ } elseif (false === $values[$id] = include $value->file) {
+ unset($values[$id], $this->values[$id]);
+ $missingIds[] = $id;
+ }
+ if (!$this->appendOnly) {
+ unset($this->values[$id]);
+ }
+ }
+
+ if (!$missingIds) {
+ return $values;
+ }
+
+ set_error_handler($this->includeHandler);
+ try {
+ $getExpiry = true;
+
+ foreach ($missingIds as $k => $id) {
+ try {
+ $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
+
+ if (isset(self::$valuesCache[$file])) {
+ [$expiresAt, $this->values[$id]] = self::$valuesCache[$file];
+ } elseif (\is_array($expiresAt = include $file)) {
+ if ($this->appendOnly) {
+ self::$valuesCache[$file] = $expiresAt;
+ }
+
+ [$expiresAt, $this->values[$id]] = $expiresAt;
+ } elseif ($now < $expiresAt) {
+ $this->values[$id] = new LazyValue($file);
+ }
+
+ if ($now >= $expiresAt) {
+ unset($this->values[$id], $missingIds[$k], self::$valuesCache[$file]);
+ }
+ } catch (\ErrorException $e) {
+ unset($missingIds[$k]);
+ }
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ $ids = $missingIds;
+ $missingIds = [];
+ goto begin;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ if ($this->appendOnly && isset($this->values[$id])) {
+ return true;
+ }
+
+ set_error_handler($this->includeHandler);
+ try {
+ $file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
+ $getExpiry = true;
+
+ if (isset(self::$valuesCache[$file])) {
+ [$expiresAt, $value] = self::$valuesCache[$file];
+ } elseif (\is_array($expiresAt = include $file)) {
+ if ($this->appendOnly) {
+ self::$valuesCache[$file] = $expiresAt;
+ }
+
+ [$expiresAt, $value] = $expiresAt;
+ } elseif ($this->appendOnly) {
+ $value = new LazyValue($file);
+ }
+ } catch (\ErrorException $e) {
+ return false;
+ } finally {
+ restore_error_handler();
+ }
+ if ($this->appendOnly) {
+ $now = 0;
+ $this->values[$id] = $value;
+ } else {
+ $now = time();
+ }
+
+ return $now < $expiresAt;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ $ok = true;
+ $expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
+ $allowCompile = self::isSupported();
+
+ foreach ($values as $key => $value) {
+ unset($this->values[$key]);
+ $isStaticValue = true;
+ if (null === $value) {
+ $value = "'N;'";
+ } elseif (\is_object($value) || \is_array($value)) {
+ try {
+ $value = VarExporter::export($value, $isStaticValue);
+ } 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);
+ }
+
+ $encodedKey = rawurlencode($key);
+
+ if ($isStaticValue) {
+ $value = "return [{$expiry}, {$value}];";
+ } elseif ($this->appendOnly) {
+ $value = "return [{$expiry}, static function () { return {$value}; }];";
+ } else {
+ // We cannot use a closure here because of https://bugs.php.net/76982
+ $value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
+ $value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};";
+ }
+
+ $file = $this->files[$key] = $this->getFile($key, true);
+ // Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
+ $ok = $this->write($file, "directory)) {
+ throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory));
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ $this->values = [];
+
+ return $this->doCommonClear($namespace);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ foreach ($ids as $id) {
+ unset($this->values[$id]);
+ }
+
+ return $this->doCommonDelete($ids);
+ }
+
+ protected function doUnlink(string $file)
+ {
+ unset(self::$valuesCache[$file]);
+
+ if (self::isSupported()) {
+ @opcache_invalidate($file, true);
+ }
+
+ return @unlink($file);
+ }
+
+ private function getFileKey(string $file): string
+ {
+ if (!$h = @fopen($file, 'r')) {
+ return '';
+ }
+
+ $encodedKey = substr(fgets($h), 8);
+ fclose($h);
+
+ return rawurldecode(rtrim($encodedKey));
+ }
+}
+
+/**
+ * @internal
+ */
+class LazyValue
+{
+ public $file;
+
+ public function __construct(string $file)
+ {
+ $this->file = $file;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/ProxyAdapter.php b/vendor/symfony/cache/Adapter/ProxyAdapter.php
new file mode 100644
index 0000000..c715cad
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/ProxyAdapter.php
@@ -0,0 +1,268 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\CacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ContractsTrait;
+ use ProxyTrait;
+
+ private $namespace = '';
+ private $namespaceLen;
+ private $poolHash;
+ private $defaultLifetime;
+
+ 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);
+ if ('' !== $namespace) {
+ \assert('' !== CacheItem::validateKey($namespace));
+ $this->namespace = $namespace;
+ }
+ $this->namespaceLen = \strlen($namespace);
+ $this->defaultLifetime = $defaultLifetime;
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $innerItem, $poolHash) {
+ $item = new CacheItem();
+ $item->key = $key;
+
+ 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);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $item = $this->pool->getItem($this->getId($key));
+
+ return (self::$createCacheItem)($key, $item, $this->poolHash);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ if ($this->namespaceLen) {
+ foreach ($keys as $i => $key) {
+ $keys[$i] = $this->getId($key);
+ }
+ }
+
+ return $this->generateItems($this->pool->getItems($keys));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ return $this->pool->hasItem($this->getId($key));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ 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)
+ {
+ return $this->pool->deleteItem($this->getId($key));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ if ($this->namespaceLen) {
+ foreach ($keys as $i => $key) {
+ $keys[$i] = $this->getId($key);
+ }
+ }
+
+ return $this->pool->deleteItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ return $this->doSave($item, __FUNCTION__);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ return $this->doSave($item, __FUNCTION__);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ return $this->pool->commit();
+ }
+
+ private function doSave(CacheItemInterface $item, string $method)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $item = (array) $item;
+ if (null === $item["\0*\0expiry"] && 0 < $this->defaultLifetime) {
+ $item["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
+ }
+
+ if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
+ $innerItem = $item["\0*\0innerItem"];
+ } 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
+ $innerItem = (self::$createCacheItem)($this->namespace.$item["\0*\0key"], null, $this->poolHash);
+ } else {
+ $innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
+ }
+
+ (self::$setInnerItem)($innerItem, $item);
+
+ return $this->pool->$method($innerItem);
+ }
+
+ private function generateItems(iterable $items): \Generator
+ {
+ $f = self::$createCacheItem;
+
+ foreach ($items as $key => $item) {
+ if ($this->namespaceLen) {
+ $key = substr($key, $this->namespaceLen);
+ }
+
+ yield $key => $f($key, $item, $this->poolHash);
+ }
+ }
+
+ private function getId($key): string
+ {
+ \assert('' !== CacheItem::validateKey($key));
+
+ return $this->namespace.$key;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/Psr16Adapter.php b/vendor/symfony/cache/Adapter/Psr16Adapter.php
new file mode 100644
index 0000000..a56aa39
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/Psr16Adapter.php
@@ -0,0 +1,86 @@
+
+ *
+ * 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\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 Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+
+ /**
+ * @internal
+ */
+ protected const NS_SEPARATOR = '_';
+
+ private $miss;
+
+ public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+ {
+ parent::__construct($namespace, $defaultLifetime);
+
+ $this->pool = $pool;
+ $this->miss = new \stdClass();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
+ if ($this->miss !== $value) {
+ yield $key => $value;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ return $this->pool->has($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ return $this->pool->deleteMultiple($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/RedisAdapter.php b/vendor/symfony/cache/Adapter/RedisAdapter.php
new file mode 100644
index 0000000..eb5950e
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/RedisAdapter.php
@@ -0,0 +1,32 @@
+
+ *
+ * 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\Traits\RedisClusterProxy;
+use Symfony\Component\Cache\Traits\RedisProxy;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+class RedisAdapter extends AbstractAdapter
+{
+ use RedisTrait;
+
+ /**
+ * @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($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ $this->init($redis, $namespace, $defaultLifetime, $marshaller);
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
new file mode 100644
index 0000000..865491e
--- /dev/null
+++ b/vendor/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 RedisTagAwareAdapter extends AbstractTagAwareAdapter
+{
+ use RedisTrait;
+
+ /**
+ * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
+ * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
+ */
+ private const DEFAULT_CACHE_TTL = 8640000;
+
+ /**
+ * @var string|null detected eviction policy used on Redis server
+ */
+ private $redisEvictionPolicy;
+ private $namespace;
+
+ /**
+ * @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($redis, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ if ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof ClusterInterface && !$redis->getConnection() instanceof PredisCluster) {
+ throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, get_debug_type($redis->getConnection())));
+ }
+
+ if (\defined('Redis::OPT_COMPRESSION') && ($redis instanceof \Redis || $redis instanceof \RedisArray || $redis instanceof \RedisCluster)) {
+ $compression = $redis->getOption(\Redis::OPT_COMPRESSION);
+
+ foreach (\is_array($compression) ? $compression : [$compression] as $c) {
+ if (\Redis::COMPRESSION_NONE !== $c) {
+ throw new InvalidArgumentException(sprintf('phpredis compression must be disabled when using "%s", use "%s" instead.', static::class, DeflateMarshaller::class));
+ }
+ }
+ }
+
+ $this->init($redis, $namespace, $defaultLifetime, new TagAwareMarshaller($marshaller));
+ $this->namespace = $namespace;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime, array $addTagData = [], array $delTagData = []): array
+ {
+ $eviction = $this->getRedisEvictionPolicy();
+ if ('noeviction' !== $eviction && !str_starts_with($eviction, 'volatile-')) {
+ throw new LogicException(sprintf('Redis maxmemory-policy setting "%s" is *not* supported by RedisTagAwareAdapter, use "noeviction" or "volatile-*" eviction policies.', $eviction));
+ }
+
+ // serialize values
+ if (!$serialized = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
+ $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
+ // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
+ foreach ($serialized as $id => $value) {
+ yield 'setEx' => [
+ $id,
+ 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
+ $value,
+ ];
+ }
+
+ // Add and Remove Tags
+ foreach ($addTagData as $tagId => $ids) {
+ if (!$failed || $ids = array_diff($ids, $failed)) {
+ yield 'sAdd' => array_merge([$tagId], $ids);
+ }
+ }
+
+ foreach ($delTagData as $tagId => $ids) {
+ if (!$failed || $ids = array_diff($ids, $failed)) {
+ yield 'sRem' => array_merge([$tagId], $ids);
+ }
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
+ if (is_numeric($result)) {
+ continue;
+ }
+ // setEx results
+ if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
+ $failed[] = $id;
+ }
+ }
+
+ return $failed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteYieldTags(array $ids): iterable
+ {
+ $lua = <<<'EOLUA'
+ local v = redis.call('GET', KEYS[1])
+ local e = redis.pcall('UNLINK', KEYS[1])
+
+ if type(e) ~= 'number' then
+ redis.call('DEL', KEYS[1])
+ end
+
+ if not v or v:len() <= 13 or v:byte(1) ~= 0x9D or v:byte(6) ~= 0 or v:byte(10) ~= 0x5F then
+ return ''
+ end
+
+ return v:sub(14, 13 + v:byte(13) + v:byte(12) * 256 + v:byte(11) * 65536)
+EOLUA;
+
+ $results = $this->pipeline(function () use ($ids, $lua) {
+ foreach ($ids as $id) {
+ yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id] : [$lua, [$id], 1];
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ if ($result instanceof \RedisException || $result instanceof ErrorInterface) {
+ CacheItem::log($this->logger, 'Failed to delete key "{key}": '.$result->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $result]);
+
+ continue;
+ }
+
+ try {
+ yield $id => !\is_string($result) || '' === $result ? [] : $this->marshaller->unmarshall($result);
+ } catch (\Exception $e) {
+ yield $id => [];
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDeleteTagRelations(array $tagData): bool
+ {
+ $results = $this->pipeline(static function () use ($tagData) {
+ foreach ($tagData as $tagId => $idList) {
+ array_unshift($idList, $tagId);
+ yield 'sRem' => $idList;
+ }
+ });
+ foreach ($results as $result) {
+ // no-op
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ // This script scans the set of items linked to tag: it empties the set
+ // and removes the linked items. When the set is still not empty after
+ // the scan, it means we're in cluster mode and that the linked items
+ // are on other nodes: we move the links to a temporary set and we
+ // gargage collect that set from the client side.
+
+ $lua = <<<'EOLUA'
+ redis.replicate_commands()
+
+ local cursor = '0'
+ local id = KEYS[1]
+ repeat
+ local result = redis.call('SSCAN', id, cursor, 'COUNT', 5000);
+ cursor = result[1];
+ local rems = {}
+
+ for _, v in ipairs(result[2]) do
+ local ok, _ = pcall(redis.call, 'DEL', ARGV[1]..v)
+ if ok then
+ table.insert(rems, v)
+ end
+ end
+ if 0 < #rems then
+ redis.call('SREM', id, unpack(rems))
+ end
+ until '0' == cursor;
+
+ redis.call('SUNIONSTORE', '{'..id..'}'..id, id)
+ redis.call('DEL', id)
+
+ return redis.call('SSCAN', '{'..id..'}'..id, '0', 'COUNT', 5000)
+EOLUA;
+
+ $results = $this->pipeline(function () use ($tagIds, $lua) {
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : '';
+ } elseif (\is_array($prefix = $this->redis->getOption(\Redis::OPT_PREFIX) ?? '')) {
+ $prefix = current($prefix);
+ }
+
+ foreach ($tagIds as $id) {
+ yield 'eval' => $this->redis instanceof \Predis\ClientInterface ? [$lua, 1, $id, $prefix] : [$lua, [$id, $prefix], 1];
+ }
+ });
+
+ $lua = <<<'EOLUA'
+ redis.replicate_commands()
+
+ local id = KEYS[1]
+ local cursor = table.remove(ARGV)
+ redis.call('SREM', '{'..id..'}'..id, unpack(ARGV))
+
+ return redis.call('SSCAN', '{'..id..'}'..id, cursor, 'COUNT', 5000)
+EOLUA;
+
+ $success = true;
+ foreach ($results as $id => $values) {
+ if ($values instanceof \RedisException || $values instanceof ErrorInterface) {
+ CacheItem::log($this->logger, 'Failed to invalidate key "{key}": '.$values->getMessage(), ['key' => substr($id, \strlen($this->namespace)), 'exception' => $values]);
+ $success = false;
+
+ continue;
+ }
+
+ [$cursor, $ids] = $values;
+
+ while ($ids || '0' !== $cursor) {
+ $this->doDelete($ids);
+
+ $evalArgs = [$id, $cursor];
+ array_splice($evalArgs, 1, 0, $ids);
+
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ array_unshift($evalArgs, $lua, 1);
+ } else {
+ $evalArgs = [$lua, $evalArgs, 1];
+ }
+
+ $results = $this->pipeline(function () use ($evalArgs) {
+ yield 'eval' => $evalArgs;
+ });
+
+ foreach ($results as [$cursor, $ids]) {
+ // no-op
+ }
+ }
+ }
+
+ return $success;
+ }
+
+ private function getRedisEvictionPolicy(): string
+ {
+ if (null !== $this->redisEvictionPolicy) {
+ return $this->redisEvictionPolicy;
+ }
+
+ $hosts = $this->getHosts();
+ $host = reset($hosts);
+ if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
+ // Predis supports info command only on the master in replication environments
+ $hosts = [$host->getClientFor('master')];
+ }
+
+ foreach ($hosts as $host) {
+ $info = $host->info('Memory');
+
+ if ($info instanceof ErrorInterface) {
+ continue;
+ }
+
+ $info = $info['Memory'] ?? $info;
+
+ return $this->redisEvictionPolicy = $info['maxmemory_policy'];
+ }
+
+ return $this->redisEvictionPolicy = '';
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/TagAwareAdapter.php b/vendor/symfony/cache/Adapter/TagAwareAdapter.php
new file mode 100644
index 0000000..ff22e5a
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TagAwareAdapter.php
@@ -0,0 +1,428 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface, LoggerAwareInterface
+{
+ use ContractsTrait;
+ use LoggerAwareTrait;
+ use ProxyTrait;
+
+ public const TAGS_PREFIX = "\0tags\0";
+
+ private $deferred = [];
+ private $tags;
+ private $knownTagVersions = [];
+ private $knownTagVersionsTtl;
+
+ 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;
+ self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
+ static function ($key, $value, CacheItem $protoItem) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->value = $value;
+ $item->expiry = $protoItem->expiry;
+ $item->poolHash = $protoItem->poolHash;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ 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->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
+ }
+ unset($itemTags[$key]);
+ } else {
+ $item->value = null;
+ $item->isHit = false;
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ self::$getTagsByKey ?? self::$getTagsByKey = \Closure::bind(
+ static function ($deferred) {
+ $tagsByKey = [];
+ foreach ($deferred as $key => $item) {
+ $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
+ $item->metadata = $item->newMetadata;
+ }
+
+ return $tagsByKey;
+ },
+ null,
+ CacheItem::class
+ );
+ self::$saveTags ?? self::$saveTags = \Closure::bind(
+ static function (AdapterInterface $tagsAdapter, array $tags) {
+ ksort($tags);
+
+ foreach ($tags as $v) {
+ $v->expiry = 0;
+ $tagsAdapter->saveDeferred($v);
+ }
+
+ return $tagsAdapter->commit();
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ $ids = [];
+ foreach ($tags as $tag) {
+ \assert('' !== CacheItem::validateKey($tag));
+ unset($this->knownTagVersions[$tag]);
+ $ids[] = $tag.static::TAGS_PREFIX;
+ }
+
+ return !$tags || $this->tags->deleteItems($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ if (\is_string($key) && isset($this->deferred[$key])) {
+ $this->commit();
+ }
+
+ if (!$this->pool->hasItem($key)) {
+ return false;
+ }
+
+ $itemTags = $this->pool->getItem(static::TAGS_PREFIX.$key);
+
+ if (!$itemTags->isHit()) {
+ return false;
+ }
+
+ if (!$itemTags = $itemTags->get()) {
+ return true;
+ }
+
+ foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
+ if ($itemTags[$tag] !== $version) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ foreach ($this->getItems([$key]) as $item) {
+ return $item;
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ $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) {
+ $this->pool->getItems($keys); // Should throw an exception
+
+ throw $e;
+ }
+
+ return $this->generateItems($items, $tagKeys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '')
+ {
+ 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)
+ {
+ return $this->deleteItems([$key]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ foreach ($keys as $key) {
+ if ('' !== $key && \is_string($key)) {
+ $keys[] = static::TAGS_PREFIX.$key;
+ }
+ }
+
+ return $this->pool->deleteItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ 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__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->commit();
+ }
+
+ private function generateItems(iterable $items, array $tagKeys): \Generator
+ {
+ $bufferedItems = $itemTags = [];
+ $f = self::$setCacheItemTags;
+
+ foreach ($items as $key => $item) {
+ if (!$tagKeys) {
+ yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
+ continue;
+ }
+ if (!isset($tagKeys[$key])) {
+ $bufferedItems[$key] = $item;
+ continue;
+ }
+
+ unset($tagKeys[$key]);
+
+ if ($item->isHit()) {
+ $itemTags[$key] = $item->get() ?: [];
+ }
+
+ if (!$tagKeys) {
+ $tagVersions = $this->getTagVersions($itemTags);
+
+ foreach ($itemTags as $key => $tags) {
+ foreach ($tags as $tag => $version) {
+ if ($tagVersions[$tag] !== $version) {
+ unset($itemTags[$key]);
+ continue 2;
+ }
+ }
+ }
+ $tagVersions = $tagKeys = null;
+
+ foreach ($bufferedItems as $key => $item) {
+ yield $key => $f($item, static::TAGS_PREFIX.$key, $itemTags);
+ }
+ $bufferedItems = null;
+ }
+ }
+ }
+
+ private function getTagVersions(array $tagsByKey)
+ {
+ $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 [];
+ }
+
+ $now = microtime(true);
+ $tags = [];
+ foreach ($tagVersions as $tag => $version) {
+ $tags[$tag.static::TAGS_PREFIX] = $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;
+ }
+ }
+
+ if (!$fetchTagVersions) {
+ return $tagVersions;
+ }
+
+ $newTags = [];
+ $newVersion = null;
+ foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
+ 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/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
new file mode 100644
index 0000000..afa18d3
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TagAwareAdapterInterface.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Interface for invalidating cached items using tags.
+ *
+ * @author Nicolas Grekas
+ */
+interface TagAwareAdapterInterface extends AdapterInterface
+{
+ /**
+ * Invalidates cached items using tags.
+ *
+ * @param string[] $tags An array of tags to invalidate
+ *
+ * @return bool
+ *
+ * @throws InvalidArgumentException When $tags is not valid
+ */
+ public function invalidateTags(array $tags);
+}
diff --git a/vendor/symfony/cache/Adapter/TraceableAdapter.php b/vendor/symfony/cache/Adapter/TraceableAdapter.php
new file mode 100644
index 0000000..4b06557
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TraceableAdapter.php
@@ -0,0 +1,295 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Cache\CacheItemInterface;
+use 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.
+ *
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ * @author Nicolas Grekas
+ */
+class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
+{
+ protected $pool;
+ private $calls = [];
+
+ public function __construct(AdapterInterface $pool)
+ {
+ $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}
+ */
+ public function getItem($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ $item = $this->pool->getItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ if ($event->result[$key] = $item->isHit()) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+
+ return $item;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function hasItem($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->hasItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$key] = $this->pool->deleteItem($key);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$item->getKey()] = $this->pool->save($item);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result[$item->getKey()] = $this->pool->saveDeferred($item);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ $result = $this->pool->getItems($keys);
+ } finally {
+ $event->end = microtime(true);
+ }
+ $f = function () use ($result, $event) {
+ $event->result = [];
+ foreach ($result as $key => $item) {
+ if ($event->result[$key] = $item->isHit()) {
+ ++$event->hits;
+ } else {
+ ++$event->misses;
+ }
+ yield $key => $item;
+ }
+ };
+
+ return $f();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ 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);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItems(array $keys)
+ {
+ $event = $this->start(__FUNCTION__);
+ $event->result['keys'] = $keys;
+ try {
+ return $event->result['result'] = $this->pool->deleteItems($keys);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function commit()
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->commit();
+ } 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 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;
+ }
+
+ public function clearCalls()
+ {
+ $this->calls = [];
+ }
+
+ protected function start(string $name)
+ {
+ $this->calls[] = $event = new TraceableAdapterEvent();
+ $event->name = $name;
+ $event->start = microtime(true);
+
+ return $event;
+ }
+}
+
+class TraceableAdapterEvent
+{
+ public $name;
+ public $start;
+ public $end;
+ public $result;
+ public $hits = 0;
+ public $misses = 0;
+}
diff --git a/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.php
new file mode 100644
index 0000000..69461b8
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/TraceableTagAwareAdapter.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\Component\Cache\Adapter;
+
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * @author Robin Chalas
+ */
+class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
+{
+ public function __construct(TagAwareAdapterInterface $pool)
+ {
+ parent::__construct($pool);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ $event = $this->start(__FUNCTION__);
+ try {
+ return $event->result = $this->pool->invalidateTags($tags);
+ } finally {
+ $event->end = microtime(true);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/CHANGELOG.md b/vendor/symfony/cache/CHANGELOG.md
new file mode 100644
index 0000000..60a8627
--- /dev/null
+++ b/vendor/symfony/cache/CHANGELOG.md
@@ -0,0 +1,108 @@
+CHANGELOG
+=========
+
+5.4
+---
+
+ * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package
+ * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL
+ * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL
+
+5.3
+---
+
+ * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension
+ * add support for a custom serializer to the `ApcuAdapter` class
+
+5.2.0
+-----
+
+ * added integration with Messenger to allow computing cached values in a worker
+ * allow ISO 8601 time intervals to specify default lifetime
+
+5.1.0
+-----
+
+ * added max-items + LRU + max-lifetime capabilities to `ArrayCache`
+ * added `CouchbaseBucketAdapter`
+ * added context `cache-adapter` to log messages
+
+5.0.0
+-----
+
+ * removed all PSR-16 implementations in the `Simple` namespace
+ * removed `SimpleCacheAdapter`
+ * removed `AbstractAdapter::unserialize()`
+ * removed `CacheItem::getPreviousTags()`
+
+4.4.0
+-----
+
+ * added support for connecting to Redis Sentinel clusters
+ * added argument `$prefix` to `AdapterInterface::clear()`
+ * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
+ * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter`
+ * added `DeflateMarshaller` to compress serialized values
+ * removed support for phpredis 4 `compression`
+ * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
+ * Marked the `CacheDataCollector` class as `@final`.
+ * added `SodiumMarshaller` to encrypt/decrypt values using libsodium
+
+4.3.0
+-----
+
+ * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
+ * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
+ * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
+
+4.2.0
+-----
+
+ * added support for connecting to Redis clusters via DSN
+ * added support for configuring multiple Memcached servers via DSN
+ * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
+ * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
+ * added sub-second expiry accuracy for backends that support it
+ * added support for phpredis 4 `compression` and `tcp_keepalive` options
+ * added automatic table creation when using Doctrine DBAL with PDO-based backends
+ * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
+ * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
+ * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
+ * added `CacheCollectorPass` (originally in `FrameworkBundle`)
+ * added `CachePoolClearerPass` (originally in `FrameworkBundle`)
+ * added `CachePoolPass` (originally in `FrameworkBundle`)
+ * added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
+
+3.4.0
+-----
+
+ * added using options from Memcached DSN
+ * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
+ * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait
+ * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and
+ ChainCache implement PruneableInterface and support manual stale cache pruning
+
+3.3.0
+-----
+
+ * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
+ * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
+ * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
+ * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
+ * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16)
+
+3.2.0
+-----
+
+ * added TagAwareAdapter for tags-based invalidation
+ * added PdoAdapter with PDO and Doctrine DBAL support
+ * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only)
+ * added NullAdapter
+
+3.1.0
+-----
+
+ * added the component with strict PSR-6 implementations
+ * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter
+ * added AbstractAdapter, ChainAdapter and ProxyAdapter
+ * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache
diff --git a/vendor/symfony/cache/CacheItem.php b/vendor/symfony/cache/CacheItem.php
new file mode 100644
index 0000000..cf39b4c
--- /dev/null
+++ b/vendor/symfony/cache/CacheItem.php
@@ -0,0 +1,192 @@
+
+ *
+ * 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\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Contracts\Cache\ItemInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+final class CacheItem implements ItemInterface
+{
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+ protected $key;
+ protected $value;
+ protected $isHit = false;
+ protected $expiry;
+ protected $metadata = [];
+ protected $newMetadata = [];
+ protected $innerItem;
+ protected $poolHash;
+ protected $isTaggable = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getKey(): string
+ {
+ return $this->key;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ public function get()
+ {
+ return $this->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isHit(): bool
+ {
+ return $this->isHit;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function set($value): self
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function expiresAt($expiration): self
+ {
+ if (null === $expiration) {
+ $this->expiry = null;
+ } elseif ($expiration instanceof \DateTimeInterface) {
+ $this->expiry = (float) $expiration->format('U.u');
+ } else {
+ throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', get_debug_type($expiration)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function expiresAfter($time): self
+ {
+ if (null === $time) {
+ $this->expiry = null;
+ } elseif ($time instanceof \DateInterval) {
+ $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
+ } elseif (\is_int($time)) {
+ $this->expiry = $time + microtime(true);
+ } else {
+ throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tag($tags): ItemInterface
+ {
+ 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) && !(\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)));
+ }
+ $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, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS));
+ }
+ $this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata(): array
+ {
+ return $this->metadata;
+ }
+
+ /**
+ * Validates a cache key according to PSR-6.
+ *
+ * @param mixed $key The key to validate
+ *
+ * @throws InvalidArgumentException When $key is not valid
+ */
+ public static function validateKey($key): string
+ {
+ if (!\is_string($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, self::RESERVED_CHARACTERS)) {
+ throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS));
+ }
+
+ return $key;
+ }
+
+ /**
+ * Internal logging helper.
+ *
+ * @internal
+ */
+ public static function log(?LoggerInterface $logger, string $message, array $context = [])
+ {
+ if ($logger) {
+ $logger->warning($message, $context);
+ } else {
+ $replace = [];
+ foreach ($context as $k => $v) {
+ if (is_scalar($v)) {
+ $replace['{'.$k.'}'] = $v;
+ }
+ }
+ @trigger_error(strtr($message, $replace), \E_USER_WARNING);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/DataCollector/CacheDataCollector.php b/vendor/symfony/cache/DataCollector/CacheDataCollector.php
new file mode 100644
index 0000000..9590436
--- /dev/null
+++ b/vendor/symfony/cache/DataCollector/CacheDataCollector.php
@@ -0,0 +1,185 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\DataCollector;
+
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableAdapterEvent;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
+
+/**
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ *
+ * @final
+ */
+class CacheDataCollector extends DataCollector implements LateDataCollectorInterface
+{
+ /**
+ * @var TraceableAdapter[]
+ */
+ private $instances = [];
+
+ public function addInstance(string $name, TraceableAdapter $instance)
+ {
+ $this->instances[$name] = $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function collect(Request $request, Response $response, \Throwable $exception = null)
+ {
+ $empty = ['calls' => [], 'config' => [], 'options' => [], 'statistics' => []];
+ $this->data = ['instances' => $empty, 'total' => $empty];
+ foreach ($this->instances as $name => $instance) {
+ $this->data['instances']['calls'][$name] = $instance->getCalls();
+ }
+
+ $this->data['instances']['statistics'] = $this->calculateStatistics();
+ $this->data['total']['statistics'] = $this->calculateTotalStatistics();
+ }
+
+ public function reset()
+ {
+ $this->data = [];
+ foreach ($this->instances as $instance) {
+ $instance->clearCalls();
+ }
+ }
+
+ public function lateCollect()
+ {
+ $this->data['instances']['calls'] = $this->cloneVar($this->data['instances']['calls']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName(): string
+ {
+ return 'cache';
+ }
+
+ /**
+ * Method returns amount of logged Cache reads: "get" calls.
+ */
+ public function getStatistics(): array
+ {
+ return $this->data['instances']['statistics'];
+ }
+
+ /**
+ * Method returns the statistic totals.
+ */
+ public function getTotals(): array
+ {
+ return $this->data['total']['statistics'];
+ }
+
+ /**
+ * Method returns all logged Cache call objects.
+ *
+ * @return mixed
+ */
+ public function getCalls()
+ {
+ return $this->data['instances']['calls'];
+ }
+
+ private function calculateStatistics(): array
+ {
+ $statistics = [];
+ foreach ($this->data['instances']['calls'] as $name => $calls) {
+ $statistics[$name] = [
+ 'calls' => 0,
+ 'time' => 0,
+ 'reads' => 0,
+ 'writes' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 0,
+ ];
+ /** @var TraceableAdapterEvent $call */
+ foreach ($calls as $call) {
+ ++$statistics[$name]['calls'];
+ $statistics[$name]['time'] += $call->end - $call->start;
+ if ('get' === $call->name) {
+ ++$statistics[$name]['reads'];
+ if ($call->hits) {
+ ++$statistics[$name]['hits'];
+ } else {
+ ++$statistics[$name]['misses'];
+ ++$statistics[$name]['writes'];
+ }
+ } elseif ('getItem' === $call->name) {
+ ++$statistics[$name]['reads'];
+ if ($call->hits) {
+ ++$statistics[$name]['hits'];
+ } else {
+ ++$statistics[$name]['misses'];
+ }
+ } elseif ('getItems' === $call->name) {
+ $statistics[$name]['reads'] += $call->hits + $call->misses;
+ $statistics[$name]['hits'] += $call->hits;
+ $statistics[$name]['misses'] += $call->misses;
+ } elseif ('hasItem' === $call->name) {
+ ++$statistics[$name]['reads'];
+ if (false === $call->result) {
+ ++$statistics[$name]['misses'];
+ } else {
+ ++$statistics[$name]['hits'];
+ }
+ } elseif ('save' === $call->name) {
+ ++$statistics[$name]['writes'];
+ } elseif ('deleteItem' === $call->name) {
+ ++$statistics[$name]['deletes'];
+ }
+ }
+ if ($statistics[$name]['reads']) {
+ $statistics[$name]['hit_read_ratio'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2);
+ } else {
+ $statistics[$name]['hit_read_ratio'] = null;
+ }
+ }
+
+ return $statistics;
+ }
+
+ private function calculateTotalStatistics(): array
+ {
+ $statistics = $this->getStatistics();
+ $totals = [
+ 'calls' => 0,
+ 'time' => 0,
+ 'reads' => 0,
+ 'writes' => 0,
+ 'deletes' => 0,
+ 'hits' => 0,
+ 'misses' => 0,
+ ];
+ foreach ($statistics as $name => $values) {
+ foreach ($totals as $key => $value) {
+ $totals[$key] += $statistics[$name][$key];
+ }
+ }
+ if ($totals['reads']) {
+ $totals['hit_read_ratio'] = round(100 * $totals['hits'] / $totals['reads'], 2);
+ } else {
+ $totals['hit_read_ratio'] = null;
+ }
+
+ return $totals;
+ }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
new file mode 100644
index 0000000..843232e
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CacheCollectorPass.php
@@ -0,0 +1,94 @@
+
+ *
+ * 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\TagAwareAdapterInterface;
+use Symfony\Component\Cache\Adapter\TraceableAdapter;
+use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Inject a data collector to all the cache services to be able to get detailed statistics.
+ *
+ * @author Tobias Nyholm
+ */
+class CacheCollectorPass implements CompilerPassInterface
+{
+ private $dataCollectorCacheId;
+ private $cachePoolTag;
+ private $cachePoolRecorderInnerSuffix;
+
+ public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/cache', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->dataCollectorCacheId = $dataCollectorCacheId;
+ $this->cachePoolTag = $cachePoolTag;
+ $this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->dataCollectorCacheId)) {
+ return;
+ }
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
+ $poolName = $attributes[0]['name'] ?? $id;
+
+ $this->addToCollector($id, $poolName, $container);
+ }
+ }
+
+ private function addToCollector(string $id, string $name, ContainerBuilder $container)
+ {
+ $definition = $container->getDefinition($id);
+ if ($definition->isAbstract()) {
+ return;
+ }
+
+ $collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
+ $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
+ $recorder->setTags($definition->getTags());
+ if (!$definition->isPublic() || !$definition->isPrivate()) {
+ $recorder->setPublic($definition->isPublic());
+ }
+ $recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
+
+ foreach ($definition->getMethodCalls() as [$method, $args]) {
+ if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) {
+ continue;
+ }
+ if ([new Reference($id), 'setCallbackWrapper'] == $args[0]->getArguments()[2]->getFactory()) {
+ $args[0]->getArguments()[2]->setFactory([new Reference($innerId), 'setCallbackWrapper']);
+ }
+ }
+
+ $definition->setTags([]);
+ $definition->setPublic(false);
+
+ $container->setDefinition($innerId, $definition);
+ $container->setDefinition($id, $recorder);
+
+ // Tell the collector to add the new instance
+ $collectorDefinition->addMethodCall('addInstance', [$name, new Reference($id)]);
+ $collectorDefinition->setPublic(false);
+ }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.php
new file mode 100644
index 0000000..c9b04ad
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CachePoolClearerPass.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\Cache\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Nicolas Grekas
+ */
+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}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ $container->getParameterBag()->remove('cache.prefix.seed');
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
+ $clearer = $container->getDefinition($id);
+ $pools = [];
+ foreach ($clearer->getArgument(0) as $name => $ref) {
+ if ($container->hasDefinition($ref)) {
+ $pools[$name] = new Reference($ref);
+ }
+ }
+ $clearer->replaceArgument(0, $pools);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/DependencyInjection/CachePoolPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPass.php
new file mode 100644
index 0000000..14ac2bd
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
new file mode 100644
index 0000000..86a1906
--- /dev/null
+++ b/vendor/symfony/cache/DependencyInjection/CachePoolPrunerPass.php
@@ -0,0 +1,64 @@
+
+ *
+ * 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\PruneableInterface;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * @author Rob Frawley 2nd
+ */
+class CachePoolPrunerPass implements CompilerPassInterface
+{
+ private $cacheCommandServiceId;
+ private $cachePoolTag;
+
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->cacheCommandServiceId)) {
+ return;
+ }
+
+ $services = [];
+
+ foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
+ $class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
+
+ if (!$reflection = $container->getReflectionClass($class)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+
+ if ($reflection->implementsInterface(PruneableInterface::class)) {
+ $services[$id] = new Reference($id);
+ }
+ }
+
+ $container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
+ }
+}
diff --git a/vendor/symfony/cache/DoctrineProvider.php b/vendor/symfony/cache/DoctrineProvider.php
new file mode 100644
index 0000000..7b55aae
--- /dev/null
+++ b/vendor/symfony/cache/DoctrineProvider.php
@@ -0,0 +1,124 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+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
+{
+ private $pool;
+
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ return $this->pool instanceof PruneableInterface && $this->pool->prune();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if ($this->pool instanceof ResetInterface) {
+ $this->pool->reset();
+ }
+ $this->setNamespace($this->getNamespace());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ protected function doFetch($id)
+ {
+ $item = $this->pool->getItem(rawurlencode($id));
+
+ return $item->isHit() ? $item->get() : false;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doContains($id)
+ {
+ return $this->pool->hasItem(rawurlencode($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doSave($id, $data, $lifeTime = 0)
+ {
+ $item = $this->pool->getItem(rawurlencode($id));
+
+ if (0 < $lifeTime) {
+ $item->expiresAfter($lifeTime);
+ }
+
+ return $this->pool->save($item->set($data));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doDelete($id)
+ {
+ return $this->pool->deleteItem(rawurlencode($id));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ protected function doFlush()
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return array|null
+ */
+ protected function doGetStats()
+ {
+ return null;
+ }
+}
diff --git a/vendor/symfony/cache/Exception/CacheException.php b/vendor/symfony/cache/Exception/CacheException.php
new file mode 100644
index 0000000..d2e975b
--- /dev/null
+++ b/vendor/symfony/cache/Exception/CacheException.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 CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class CacheException extends \Exception implements Psr6CacheInterface
+ {
+ }
+}
diff --git a/vendor/symfony/cache/Exception/InvalidArgumentException.php b/vendor/symfony/cache/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..7f9584a
--- /dev/null
+++ b/vendor/symfony/cache/Exception/InvalidArgumentException.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\InvalidArgumentException as Psr6CacheInterface;
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
+
+if (interface_exists(SimpleCacheInterface::class)) {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
+ {
+ }
+} else {
+ class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
+ {
+ }
+}
diff --git a/vendor/symfony/cache/Exception/LogicException.php b/vendor/symfony/cache/Exception/LogicException.php
new file mode 100644
index 0000000..9ffa7ed
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/LICENSE b/vendor/symfony/cache/LICENSE
new file mode 100644
index 0000000..7fa9539
--- /dev/null
+++ b/vendor/symfony/cache/LICENSE
@@ -0,0 +1,19 @@
+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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/cache/LockRegistry.php b/vendor/symfony/cache/LockRegistry.php
new file mode 100644
index 0000000..23e5b4e
--- /dev/null
+++ b/vendor/symfony/cache/LockRegistry.php
@@ -0,0 +1,163 @@
+
+ *
+ * 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;
+
+ /**
+ * 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);
+ }
+
+ 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]);
+ }
+ static $signalingException, $signalingCallback;
+ $signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
+ $signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; };
+
+ try {
+ $value = $pool->get($item->getKey(), $signalingCallback, 0);
+ $logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
+ $save = false;
+
+ return $value;
+ } catch (\Exception $e) {
+ if ($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/vendor/symfony/cache/Marshaller/DefaultMarshaller.php b/vendor/symfony/cache/Marshaller/DefaultMarshaller.php
new file mode 100644
index 0000000..3202dd6
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Marshaller/DeflateMarshaller.php b/vendor/symfony/cache/Marshaller/DeflateMarshaller.php
new file mode 100644
index 0000000..5544806
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Marshaller/MarshallerInterface.php b/vendor/symfony/cache/Marshaller/MarshallerInterface.php
new file mode 100644
index 0000000..cdd6c40
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Marshaller/SodiumMarshaller.php b/vendor/symfony/cache/Marshaller/SodiumMarshaller.php
new file mode 100644
index 0000000..dbf486a
--- /dev/null
+++ b/vendor/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 SodiumMarshaller implements MarshallerInterface
+{
+ private $marshaller;
+ private $decryptionKeys;
+
+ /**
+ * @param string[] $decryptionKeys The key at index "0" is required and is used to decrypt and encrypt values;
+ * more rotating keys can be provided to decrypt values;
+ * each key must be generated using sodium_crypto_box_keypair()
+ */
+ public function __construct(array $decryptionKeys, MarshallerInterface $marshaller = null)
+ {
+ if (!self::isSupported()) {
+ throw new CacheException('The "sodium" PHP extension is not loaded.');
+ }
+
+ if (!isset($decryptionKeys[0])) {
+ throw new InvalidArgumentException('At least one decryption key must be provided at index "0".');
+ }
+
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ $this->decryptionKeys = $decryptionKeys;
+ }
+
+ public static function isSupported(): bool
+ {
+ return \function_exists('sodium_crypto_box_seal');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function marshall(array $values, ?array &$failed): array
+ {
+ $encryptionKey = sodium_crypto_box_publickey($this->decryptionKeys[0]);
+
+ $encryptedValues = [];
+ foreach ($this->marshaller->marshall($values, $failed) as $k => $v) {
+ $encryptedValues[$k] = sodium_crypto_box_seal($v, $encryptionKey);
+ }
+
+ return $encryptedValues;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unmarshall(string $value)
+ {
+ foreach ($this->decryptionKeys as $k) {
+ if (false !== $decryptedValue = @sodium_crypto_box_seal_open($value, $k)) {
+ $value = $decryptedValue;
+ break;
+ }
+ }
+
+ return $this->marshaller->unmarshall($value);
+ }
+}
diff --git a/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php
new file mode 100644
index 0000000..5d1e303
--- /dev/null
+++ b/vendor/symfony/cache/Marshaller/TagAwareMarshaller.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Marshaller;
+
+/**
+ * A marshaller optimized for data structures generated by AbstractTagAwareAdapter.
+ *
+ * @author Nicolas Grekas
+ */
+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/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php b/vendor/symfony/cache/Messenger/EarlyExpirationDispatcher.php
new file mode 100644
index 0000000..6f11b8b
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php b/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php
new file mode 100644
index 0000000..1f0bd56
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php b/vendor/symfony/cache/Messenger/EarlyExpirationMessage.php
new file mode 100644
index 0000000..e25c07e
--- /dev/null
+++ b/vendor/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/vendor/symfony/cache/PruneableInterface.php b/vendor/symfony/cache/PruneableInterface.php
new file mode 100644
index 0000000..4261525
--- /dev/null
+++ b/vendor/symfony/cache/PruneableInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+/**
+ * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items.
+ */
+interface PruneableInterface
+{
+ /**
+ * @return bool
+ */
+ public function prune();
+}
diff --git a/vendor/symfony/cache/Psr16Cache.php b/vendor/symfony/cache/Psr16Cache.php
new file mode 100644
index 0000000..28c7de6
--- /dev/null
+++ b/vendor/symfony/cache/Psr16Cache.php
@@ -0,0 +1,289 @@
+
+ *
+ * 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\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\Exception\InvalidArgumentException;
+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 Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+
+ private const METADATA_EXPIRY_OFFSET = 1527506807;
+
+ private $createCacheItem;
+ private $cacheItemPrototype;
+
+ public function __construct(CacheItemPoolInterface $pool)
+ {
+ $this->pool = $pool;
+
+ if (!$pool instanceof AdapterInterface) {
+ return;
+ }
+ $cacheItemPrototype = &$this->cacheItemPrototype;
+ $createCacheItem = \Closure::bind(
+ static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
+ $item = clone $cacheItemPrototype;
+ $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;
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
+ if (null === $this->cacheItemPrototype) {
+ $this->get($allowInt && \is_int($key) ? (string) $key : $key);
+ }
+ $this->createCacheItem = $createCacheItem;
+
+ return $createCacheItem($key, null, $allowInt)->set($value);
+ };
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ try {
+ $item = $this->pool->getItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ if (null === $this->cacheItemPrototype) {
+ $this->cacheItemPrototype = clone $item;
+ $this->cacheItemPrototype->set(null);
+ }
+
+ return $item->isHit() ? $item->get() : $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ try {
+ if (null !== $f = $this->createCacheItem) {
+ $item = $f($key, $value);
+ } else {
+ $item = $this->pool->getItem($key)->set($value);
+ }
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ if (null !== $ttl) {
+ $item->expiresAfter($ttl);
+ }
+
+ return $this->pool->save($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ try {
+ return $this->pool->deleteItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear()
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@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.', get_debug_type($keys)));
+ }
+
+ try {
+ $items = $this->pool->getItems($keys);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ $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) {
+ 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;
+ }
+
+ /**
+ * {@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.', get_debug_type($values)));
+ }
+ $items = [];
+
+ try {
+ if (null !== $f = $this->createCacheItem) {
+ $valuesIsArray = false;
+ foreach ($values as $key => $value) {
+ $items[$key] = $f($key, $value, true);
+ }
+ } elseif ($valuesIsArray) {
+ $items = [];
+ foreach ($values as $key => $value) {
+ $items[] = (string) $key;
+ }
+ $items = $this->pool->getItems($items);
+ } else {
+ foreach ($values as $key => $value) {
+ if (\is_int($key)) {
+ $key = (string) $key;
+ }
+ $items[$key] = $this->pool->getItem($key)->set($value);
+ }
+ }
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ $ok = true;
+
+ foreach ($items as $key => $item) {
+ if ($valuesIsArray) {
+ $item->set($values[$key]);
+ }
+ if (null !== $ttl) {
+ $item->expiresAfter($ttl);
+ }
+ $ok = $this->pool->saveDeferred($item) && $ok;
+ }
+
+ return $this->pool->commit() && $ok;
+ }
+
+ /**
+ * {@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.', get_debug_type($keys)));
+ }
+
+ try {
+ return $this->pool->deleteItems($keys);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ try {
+ return $this->pool->hasItem($key);
+ } catch (SimpleCacheException $e) {
+ throw $e;
+ } catch (Psr6CacheException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/README.md b/vendor/symfony/cache/README.md
new file mode 100644
index 0000000..7405205
--- /dev/null
+++ b/vendor/symfony/cache/README.md
@@ -0,0 +1,19 @@
+Symfony PSR-6 implementation for caching
+========================================
+
+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)
diff --git a/vendor/symfony/cache/ResettableInterface.php b/vendor/symfony/cache/ResettableInterface.php
new file mode 100644
index 0000000..7b0a853
--- /dev/null
+++ b/vendor/symfony/cache/ResettableInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache;
+
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * Resets a pool's local state.
+ */
+interface ResettableInterface extends ResetInterface
+{
+}
diff --git a/vendor/symfony/cache/Traits/AbstractAdapterTrait.php b/vendor/symfony/cache/Traits/AbstractAdapterTrait.php
new file mode 100644
index 0000000..f0173c4
--- /dev/null
+++ b/vendor/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 , mixed , bool )
+ */
+ private static $createCacheItem;
+
+ /**
+ * @var \Closure needs to be set by class, signature is function(array , string , array <&expiredIds>)
+ */
+ private static $mergeByLifetime;
+
+ private $namespace = '';
+ private $defaultLifetime;
+ private $namespaceVersion = '';
+ private $versioningIsEnabled = false;
+ private $deferred = [];
+ private $ids = [];
+
+ /**
+ * @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
+ */
+ 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
+ */
+ abstract protected function doHave(string $id);
+
+ /**
+ * Deletes all items in the pool.
+ *
+ * @param string $namespace The prefix used for all identifiers managed by this pool
+ *
+ * @return bool
+ */
+ abstract protected function doClear(string $namespace);
+
+ /**
+ * Removes multiple items from the pool.
+ *
+ * @param array $ids An array of identifiers that should be removed from the pool
+ *
+ * @return bool
+ */
+ 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, int $lifetime);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ 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: '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function clear(string $prefix = '')
+ {
+ $this->deferred = [];
+ if ($cleared = $this->versioningIsEnabled) {
+ if ('' === $namespaceVersionToClear = $this->namespaceVersion) {
+ foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
+ $namespaceVersionToClear = $v;
+ }
+ }
+ $namespaceToClear = $this->namespace.$namespaceVersionToClear;
+ $namespaceVersion = self::formatNamespaceVersion(mt_rand());
+ try {
+ $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
+ } catch (\Exception $e) {
+ }
+ if (true !== $e && [] !== $e) {
+ $cleared = false;
+ $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
+ } else {
+ $this->namespaceVersion = $namespaceVersion;
+ $this->ids = [];
+ }
+ } else {
+ $namespaceToClear = $this->namespace.$prefix;
+ }
+
+ try {
+ return $this->doClear($namespaceToClear) || $cleared;
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function deleteItem($key)
+ {
+ return $this->deleteItems([$key]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ 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) {
+ }
+ $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $id = $this->getId($key);
+
+ if (isset($this->deferred[$key])) {
+ $this->commit();
+ }
+
+ $isHit = false;
+ $value = null;
+
+ try {
+ foreach ($this->doFetch([$id]) as $value) {
+ $isHit = true;
+ }
+
+ return (self::$createCacheItem)($key, $value, $isHit);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ }
+
+ return (self::$createCacheItem)($key, null, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ $ids = [];
+ $commit = false;
+
+ foreach ($keys as $key) {
+ $ids[] = $this->getId($key);
+ $commit = $commit || isset($this->deferred[$key]);
+ }
+
+ if ($commit) {
+ $this->commit();
+ }
+
+ try {
+ $items = $this->doFetch($ids);
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ $items = [];
+ }
+ $ids = array_combine($ids, $keys);
+
+ return $this->generateItems($items, $ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return bool
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ if (!$item instanceof CacheItem) {
+ return false;
+ }
+ $this->deferred[$item->getKey()] = $item;
+
+ return true;
+ }
+
+ /**
+ * 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.
+ *
+ * @return bool the previous state of versioning
+ */
+ public function enableVersioning(bool $enable = true)
+ {
+ $wasEnabled = $this->versioningIsEnabled;
+ $this->versioningIsEnabled = $enable;
+ $this->namespaceVersion = '';
+ $this->ids = [];
+
+ return $wasEnabled;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if ($this->deferred) {
+ $this->commit();
+ }
+ $this->namespaceVersion = '';
+ $this->ids = [];
+ }
+
+ /**
+ * @return array
+ */
+ 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(iterable $items, array &$keys): \Generator
+ {
+ $f = self::$createCacheItem;
+
+ try {
+ foreach ($items as $id => $value) {
+ if (!isset($keys[$id])) {
+ throw new InvalidArgumentException(sprintf('Could not match value id "%s" to keys "%s".', $id, implode('", "', $keys)));
+ }
+ $key = $keys[$id];
+ unset($keys[$id]);
+ yield $key => $f($key, $value, true);
+ }
+ } catch (\Exception $e) {
+ CacheItem::log($this->logger, 'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e, 'cache-adapter' => get_debug_type($this)]);
+ }
+
+ foreach ($keys as $key) {
+ yield $key => $f($key, null, false);
+ }
+ }
+
+ private function getId($key)
+ {
+ if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
+ $this->ids = [];
+ $this->namespaceVersion = '1'.static::NS_SEPARATOR;
+ try {
+ foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
+ $this->namespaceVersion = $v;
+ }
+ $e = true;
+ if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
+ $this->namespaceVersion = self::formatNamespaceVersion(time());
+ $e = $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
+ }
+ } catch (\Exception $e) {
+ }
+ if (true !== $e && [] !== $e) {
+ $message = 'Failed to save the new namespace'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
+ }
+ }
+
+ if (\is_string($key) && isset($this->ids[$key])) {
+ return $this->namespace.$this->namespaceVersion.$this->ids[$key];
+ }
+ \assert('' !== CacheItem::validateKey($key));
+ $this->ids[$key] = $key;
+
+ if (\count($this->ids) > 1000) {
+ array_splice($this->ids, 0, 500); // stop memory leak if there are many keys
+ }
+
+ if (null === $this->maxIdLength) {
+ return $this->namespace.$this->namespaceVersion.$key;
+ }
+ if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
+ // Use MD5 to favor speed over security, which is not an issue here
+ $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
+ $id = $this->namespace.$this->namespaceVersion.$id;
+ }
+
+ return $id;
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleUnserializeCallback(string $class)
+ {
+ throw new \DomainException('Class not found: '.$class);
+ }
+
+ private static function formatNamespaceVersion(int $value): string
+ {
+ return strtr(substr_replace(base64_encode(pack('V', $value)), static::NS_SEPARATOR, 5), '/', '_');
+ }
+}
diff --git a/vendor/symfony/cache/Traits/ContractsTrait.php b/vendor/symfony/cache/Traits/ContractsTrait.php
new file mode 100644
index 0000000..9fdb931
--- /dev/null
+++ b/vendor/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 = [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/vendor/symfony/cache/Traits/FilesystemCommonTrait.php b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php
new file mode 100644
index 0000000..c06cc30
--- /dev/null
+++ b/vendor/symfony/cache/Traits/FilesystemCommonTrait.php
@@ -0,0 +1,196 @@
+
+ *
+ * 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\InvalidArgumentException;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait FilesystemCommonTrait
+{
+ private $directory;
+ private $tmp;
+
+ private function init(string $namespace, ?string $directory)
+ {
+ if (!isset($directory[0])) {
+ $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
+ } else {
+ $directory = realpath($directory) ?: $directory;
+ }
+ if (isset($namespace[0])) {
+ if (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]));
+ }
+ $directory .= \DIRECTORY_SEPARATOR.$namespace;
+ } else {
+ $directory .= \DIRECTORY_SEPARATOR.'@';
+ }
+ if (!is_dir($directory)) {
+ @mkdir($directory, 0777, true);
+ }
+ $directory .= \DIRECTORY_SEPARATOR;
+ // On Windows the whole path is limited to 258 chars
+ if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) {
+ throw new InvalidArgumentException(sprintf('Cache directory too long (%s).', $directory));
+ }
+
+ $this->directory = $directory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ $ok = true;
+
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ $ok = true;
+
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
+ }
+
+ return $ok;
+ }
+
+ 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.bin2hex(random_bytes(6));
+ }
+ 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 ?: time() + 31556952); // 1 year in seconds
+ }
+
+ return rename($this->tmp, $file);
+ } finally {
+ restore_error_handler();
+ }
+ }
+
+ private function getFile(string $id, bool $mkdir = false, string $directory = null)
+ {
+ // 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 && !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(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__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ if (method_exists(parent::class, '__destruct')) {
+ parent::__destruct();
+ }
+ if (null !== $this->tmp && is_file($this->tmp)) {
+ unlink($this->tmp);
+ }
+ }
+}
diff --git a/vendor/symfony/cache/Traits/FilesystemTrait.php b/vendor/symfony/cache/Traits/FilesystemTrait.php
new file mode 100644
index 0000000..38b741f
--- /dev/null
+++ b/vendor/symfony/cache/Traits/FilesystemTrait.php
@@ -0,0 +1,124 @@
+
+ *
+ * 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;
+
+/**
+ * @author Nicolas Grekas
+ * @author Rob Frawley 2nd
+ *
+ * @internal
+ */
+trait FilesystemTrait
+{
+ use FilesystemCommonTrait;
+
+ private $marshaller;
+
+ /**
+ * @return bool
+ */
+ public function prune()
+ {
+ $time = time();
+ $pruned = true;
+
+ foreach ($this->scanHashDir($this->directory) as $file) {
+ if (!$h = @fopen($file, 'r')) {
+ continue;
+ }
+
+ if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) {
+ fclose($h);
+ $pruned = @unlink($file) && !file_exists($file) && $pruned;
+ } else {
+ fclose($h);
+ }
+ }
+
+ return $pruned;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ $values = [];
+ $now = time();
+
+ foreach ($ids as $id) {
+ $file = $this->getFile($id);
+ if (!is_file($file) || !$h = @fopen($file, 'r')) {
+ continue;
+ }
+ if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) {
+ fclose($h);
+ @unlink($file);
+ } else {
+ $i = rawurldecode(rtrim(fgets($h)));
+ $value = stream_get_contents($h);
+ fclose($h);
+ if ($i === $id) {
+ $values[$id] = $this->marshaller->unmarshall($value);
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ $file = $this->getFile($id);
+
+ return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id]));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ $expiresAt = $lifetime ? (time() + $lifetime) : 0;
+ $values = $this->marshaller->marshall($values, $failed);
+
+ foreach ($values as $id => $value) {
+ if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
+ $failed[] = $id;
+ }
+ }
+
+ if ($failed && !is_writable($this->directory)) {
+ throw new CacheException(sprintf('Cache directory is not writable (%s).', $this->directory));
+ }
+
+ 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/vendor/symfony/cache/Traits/ProxyTrait.php b/vendor/symfony/cache/Traits/ProxyTrait.php
new file mode 100644
index 0000000..c86f360
--- /dev/null
+++ b/vendor/symfony/cache/Traits/ProxyTrait.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\PruneableInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait ProxyTrait
+{
+ private $pool;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function prune()
+ {
+ return $this->pool instanceof PruneableInterface && $this->pool->prune();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ if ($this->pool instanceof ResetInterface) {
+ $this->pool->reset();
+ }
+ }
+}
diff --git a/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php b/vendor/symfony/cache/Traits/RedisClusterNodeProxy.php
new file mode 100644
index 0000000..deba74f
--- /dev/null
+++ b/vendor/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
+ *
+ * @internal
+ */
+class RedisClusterNodeProxy
+{
+ private $host;
+ private $redis;
+
+ /**
+ * @param \RedisCluster|RedisClusterProxy $redis
+ */
+ public function __construct(array $host, $redis)
+ {
+ $this->host = $host;
+ $this->redis = $redis;
+ }
+
+ public function __call(string $method, array $args)
+ {
+ return $this->redis->{$method}($this->host, ...$args);
+ }
+
+ public function scan(&$iIterator, $strPattern = null, $iCount = null)
+ {
+ return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount);
+ }
+
+ public function getOption($name)
+ {
+ return $this->redis->getOption($name);
+ }
+}
diff --git a/vendor/symfony/cache/Traits/RedisClusterProxy.php b/vendor/symfony/cache/Traits/RedisClusterProxy.php
new file mode 100644
index 0000000..73c6a4f
--- /dev/null
+++ b/vendor/symfony/cache/Traits/RedisClusterProxy.php
@@ -0,0 +1,63 @@
+
+ *
+ * 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 Alessandro Chitolina
+ *
+ * @internal
+ */
+class RedisClusterProxy
+{
+ private $redis;
+ private $initializer;
+
+ public function __construct(\Closure $initializer)
+ {
+ $this->initializer = $initializer;
+ }
+
+ public function __call(string $method, array $args)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->{$method}(...$args);
+ }
+
+ public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function scan(&$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->scan($iIterator, $strPattern, $iCount);
+ }
+
+ public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->redis ?: $this->redis = $this->initializer->__invoke();
+
+ return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+}
diff --git a/vendor/symfony/cache/Traits/RedisProxy.php b/vendor/symfony/cache/Traits/RedisProxy.php
new file mode 100644
index 0000000..ec5cfab
--- /dev/null
+++ b/vendor/symfony/cache/Traits/RedisProxy.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\Component\Cache\Traits;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class RedisProxy
+{
+ private $redis;
+ private $initializer;
+ private $ready = false;
+
+ public function __construct(\Redis $redis, \Closure $initializer)
+ {
+ $this->redis = $redis;
+ $this->initializer = $initializer;
+ }
+
+ public function __call(string $method, array $args)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->{$method}(...$args);
+ }
+
+ public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function scan(&$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->scan($iIterator, $strPattern, $iCount);
+ }
+
+ public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+
+ public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
+ {
+ $this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
+
+ return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
+ }
+}
diff --git a/vendor/symfony/cache/Traits/RedisTrait.php b/vendor/symfony/cache/Traits/RedisTrait.php
new file mode 100644
index 0000000..149c9d4
--- /dev/null
+++ b/vendor/symfony/cache/Traits/RedisTrait.php
@@ -0,0 +1,591 @@
+
+ *
+ * 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 Predis\Command\Redis\UNLINK;
+use Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Connection\Aggregate\RedisCluster;
+use Predis\Connection\Aggregate\ReplicationInterface;
+use Predis\Response\ErrorInterface;
+use Predis\Response\Status;
+use Symfony\Component\Cache\Exception\CacheException;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+
+/**
+ * @author Aurimas Niekis
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+trait RedisTrait
+{
+ private static $defaultConnectionOptions = [
+ 'class' => null,
+ 'persistent' => 0,
+ 'persistent_id' => null,
+ 'timeout' => 30,
+ 'read_timeout' => 0,
+ 'retry_interval' => 0,
+ 'tcp_keepalive' => 0,
+ 'lazy' => null,
+ 'redis_cluster' => false,
+ 'redis_sentinel' => null,
+ 'dbindex' => 0,
+ 'failover' => 'none',
+ 'ssl' => null, // see https://php.net/context.ssl
+ ];
+ private $redis;
+ private $marshaller;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis
+ */
+ private function init($redis, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller)
+ {
+ parent::__construct($namespace, $defaultLifetime);
+
+ if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
+ throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
+ }
+
+ if (!$redis instanceof \Redis && !$redis instanceof \RedisArray && !$redis instanceof \RedisCluster && !$redis instanceof \Predis\ClientInterface && !$redis instanceof RedisProxy && !$redis instanceof RedisClusterProxy) {
+ throw new InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis)));
+ }
+
+ if ($redis instanceof \Predis\ClientInterface && $redis->getOptions()->exceptions) {
+ $options = clone $redis->getOptions();
+ \Closure::bind(function () { $this->options['exceptions'] = false; }, $options, $options)();
+ $redis = new $redis($redis->getConnection(), $options);
+ }
+
+ $this->redis = $redis;
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ }
+
+ /**
+ * Creates a Redis connection using a DSN configuration.
+ *
+ * Example DSN:
+ * - redis://localhost
+ * - redis://example.com:1234
+ * - redis://secret@example.com/13
+ * - redis:///var/run/redis.sock
+ * - redis://secret@/var/run/redis.sock/13
+ *
+ * @param array $options See self::$defaultConnectionOptions
+ *
+ * @return \Redis|\RedisArray|\RedisCluster|RedisClusterProxy|RedisProxy|\Predis\ClientInterface According to the "class" option
+ *
+ * @throws InvalidArgumentException when the DSN is invalid
+ */
+ public static function createConnection(string $dsn, array $options = [])
+ {
+ if (str_starts_with($dsn, 'redis:')) {
+ $scheme = 'redis';
+ } elseif (str_starts_with($dsn, 'rediss:')) {
+ $scheme = 'rediss';
+ } else {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s" does not start with "redis:" or "rediss".', $dsn));
+ }
+
+ if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
+ throw new CacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: "%s".', $dsn));
+ }
+
+ $params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#', function ($m) use (&$auth) {
+ if (isset($m[2])) {
+ $auth = $m[2];
+
+ if ('' === $auth) {
+ $auth = null;
+ }
+ }
+
+ return 'file:'.($m[1] ?? '');
+ }, $dsn);
+
+ if (false === $params = parse_url($params)) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
+ }
+
+ $query = $hosts = [];
+
+ $tls = 'rediss' === $scheme;
+ $tcpScheme = $tls ? 'tls' : 'tcp';
+
+ if (isset($params['query'])) {
+ parse_str($params['query'], $query);
+
+ if (isset($query['host'])) {
+ if (!\is_array($hosts = $query['host'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
+ }
+ foreach ($hosts as $host => $parameters) {
+ if (\is_string($parameters)) {
+ parse_str($parameters, $parameters);
+ }
+ if (false === $i = strrpos($host, ':')) {
+ $hosts[$host] = ['scheme' => $tcpScheme, 'host' => $host, 'port' => 6379] + $parameters;
+ } elseif ($port = (int) substr($host, 1 + $i)) {
+ $hosts[$host] = ['scheme' => $tcpScheme, 'host' => substr($host, 0, $i), 'port' => $port] + $parameters;
+ } else {
+ $hosts[$host] = ['scheme' => 'unix', 'path' => substr($host, 0, $i)] + $parameters;
+ }
+ }
+ $hosts = array_values($hosts);
+ }
+ }
+
+ if (isset($params['host']) || isset($params['path'])) {
+ if (!isset($params['dbindex']) && isset($params['path'])) {
+ if (preg_match('#/(\d+)$#', $params['path'], $m)) {
+ $params['dbindex'] = $m[1];
+ $params['path'] = substr($params['path'], 0, -\strlen($m[0]));
+ } elseif (isset($params['host'])) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s", the "dbindex" parameter must be a number.', $dsn));
+ }
+ }
+
+ if (isset($params['host'])) {
+ array_unshift($hosts, ['scheme' => $tcpScheme, 'host' => $params['host'], 'port' => $params['port'] ?? 6379]);
+ } else {
+ array_unshift($hosts, ['scheme' => 'unix', 'path' => $params['path']]);
+ }
+ }
+
+ if (!$hosts) {
+ throw new InvalidArgumentException(sprintf('Invalid Redis DSN: "%s".', $dsn));
+ }
+
+ $params += $query + $options + self::$defaultConnectionOptions;
+
+ if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class)) {
+ throw new CacheException(sprintf('Redis Sentinel support requires the "predis/predis" package or the "redis" extension v5.2 or higher: "%s".', $dsn));
+ }
+
+ if ($params['redis_cluster'] && isset($params['redis_sentinel'])) {
+ throw new InvalidArgumentException(sprintf('Cannot use both "redis_cluster" and "redis_sentinel" at the same time: "%s".', $dsn));
+ }
+
+ if (null === $params['class'] && \extension_loaded('redis')) {
+ $class = $params['redis_cluster'] ? \RedisCluster::class : (1 < \count($hosts) ? \RedisArray::class : \Redis::class);
+ } else {
+ $class = $params['class'] ?? \Predis\Client::class;
+ }
+
+ if (is_a($class, \Redis::class, true)) {
+ $connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
+ $redis = new $class();
+
+ $initializer = static function ($redis) use ($connect, $params, $dsn, $auth, $hosts, $tls) {
+ $host = $hosts[0]['host'] ?? $hosts[0]['path'];
+ $port = $hosts[0]['port'] ?? 0;
+
+ if (isset($hosts[0]['host']) && $tls) {
+ $host = 'tls://'.$host;
+ }
+
+ if (isset($params['redis_sentinel'])) {
+ $sentinel = new \RedisSentinel($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout']);
+
+ if (!$address = $sentinel->getMasterAddrByName($params['redis_sentinel'])) {
+ throw new InvalidArgumentException(sprintf('Failed to retrieve master information from master name "%s" and address "%s:%d".', $params['redis_sentinel'], $host, $port));
+ }
+
+ [$host, $port] = $address;
+ }
+
+ try {
+ @$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []);
+
+ set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
+ try {
+ $isConnected = $redis->isConnected();
+ } finally {
+ restore_error_handler();
+ }
+ if (!$isConnected) {
+ $error = preg_match('/^Redis::p?connect\(\): (.*)/', $error, $error) ? sprintf(' (%s)', $error[1]) : '';
+ throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$error.'.');
+ }
+
+ if ((null !== $auth && !$redis->auth($auth))
+ || ($params['dbindex'] && !$redis->select($params['dbindex']))
+ ) {
+ $e = preg_replace('/^ERR /', '', $redis->getLastError());
+ throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e.'.');
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ } catch (\RedisException $e) {
+ throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
+ }
+
+ return true;
+ };
+
+ if ($params['lazy']) {
+ $redis = new RedisProxy($redis, $initializer);
+ } else {
+ $initializer($redis);
+ }
+ } elseif (is_a($class, \RedisArray::class, true)) {
+ foreach ($hosts as $i => $host) {
+ switch ($host['scheme']) {
+ case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break;
+ case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break;
+ default: $hosts[$i] = $host['path'];
+ }
+ }
+ $params['lazy_connect'] = $params['lazy'] ?? true;
+ $params['connect_timeout'] = $params['timeout'];
+
+ try {
+ $redis = new $class($hosts, $params);
+ } catch (\RedisClusterException $e) {
+ throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ } elseif (is_a($class, \RedisCluster::class, true)) {
+ $initializer = static function () use ($class, $params, $dsn, $hosts) {
+ foreach ($hosts as $i => $host) {
+ switch ($host['scheme']) {
+ case 'tcp': $hosts[$i] = $host['host'].':'.$host['port']; break;
+ case 'tls': $hosts[$i] = 'tls://'.$host['host'].':'.$host['port']; break;
+ default: $hosts[$i] = $host['path'];
+ }
+ }
+
+ try {
+ $redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []);
+ } catch (\RedisClusterException $e) {
+ throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
+ }
+
+ if (0 < $params['tcp_keepalive'] && \defined('Redis::OPT_TCP_KEEPALIVE')) {
+ $redis->setOption(\Redis::OPT_TCP_KEEPALIVE, $params['tcp_keepalive']);
+ }
+ switch ($params['failover']) {
+ case 'error': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR); break;
+ case 'distribute': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE); break;
+ case 'slaves': $redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES); break;
+ }
+
+ return $redis;
+ };
+
+ $redis = $params['lazy'] ? new RedisClusterProxy($initializer) : $initializer();
+ } elseif (is_a($class, \Predis\ClientInterface::class, true)) {
+ if ($params['redis_cluster']) {
+ $params['cluster'] = 'redis';
+ } elseif (isset($params['redis_sentinel'])) {
+ $params['replication'] = 'sentinel';
+ $params['service'] = $params['redis_sentinel'];
+ }
+ $params += ['parameters' => []];
+ $params['parameters'] += [
+ 'persistent' => $params['persistent'],
+ 'timeout' => $params['timeout'],
+ 'read_write_timeout' => $params['read_timeout'],
+ 'tcp_nodelay' => true,
+ ];
+ if ($params['dbindex']) {
+ $params['parameters']['database'] = $params['dbindex'];
+ }
+ if (null !== $auth) {
+ $params['parameters']['password'] = $auth;
+ }
+ if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) {
+ $hosts = $hosts[0];
+ } elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) {
+ $params['replication'] = true;
+ $hosts[0] += ['alias' => 'master'];
+ }
+ $params['exceptions'] = false;
+
+ $redis = new $class($hosts, array_diff_key($params, array_diff_key(self::$defaultConnectionOptions, ['ssl' => null])));
+ if (isset($params['redis_sentinel'])) {
+ $redis->getConnection()->setSentinelTimeout($params['timeout']);
+ }
+ } elseif (class_exists($class, false)) {
+ throw new InvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\ClientInterface".', $class));
+ } else {
+ throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
+ }
+
+ return $redis;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ if (!$ids) {
+ return [];
+ }
+
+ $result = [];
+
+ if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
+ $values = $this->pipeline(function () use ($ids) {
+ foreach ($ids as $id) {
+ yield 'get' => [$id];
+ }
+ });
+ } else {
+ $values = $this->redis->mget($ids);
+
+ if (!\is_array($values) || \count($values) !== \count($ids)) {
+ return [];
+ }
+
+ $values = array_combine($ids, $values);
+ }
+
+ foreach ($values as $id => $v) {
+ if ($v) {
+ $result[$id] = $this->marshaller->unmarshall($v);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave(string $id)
+ {
+ return (bool) $this->redis->exists($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear(string $namespace)
+ {
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : '';
+ $prefixLen = \strlen($prefix);
+ }
+
+ $cleared = true;
+ $hosts = $this->getHosts();
+ $host = reset($hosts);
+ if ($host instanceof \Predis\Client && $host->getConnection() instanceof ReplicationInterface) {
+ // Predis supports info command only on the master in replication environments
+ $hosts = [$host->getClientFor('master')];
+ }
+
+ foreach ($hosts as $host) {
+ if (!isset($namespace[0])) {
+ $cleared = $host->flushDb() && $cleared;
+ continue;
+ }
+
+ $info = $host->info('Server');
+ $info = !$info instanceof ErrorInterface ? $info['Server'] ?? $info : ['redis_version' => '2.0'];
+
+ if (!$host instanceof \Predis\ClientInterface) {
+ $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX);
+ $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? '');
+ }
+ $pattern = $prefix.$namespace.'*';
+
+ if (!version_compare($info['redis_version'], '2.8', '>=')) {
+ // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS
+ // can hang your server when it is executed against large databases (millions of items).
+ // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above.
+ $unlink = version_compare($info['redis_version'], '4.0', '>=') ? 'UNLINK' : 'DEL';
+ $args = $this->redis instanceof \Predis\ClientInterface ? [0, $pattern] : [[$pattern], 0];
+ $cleared = $host->eval("local keys=redis.call('KEYS',ARGV[1]) for i=1,#keys,5000 do redis.call('$unlink',unpack(keys,i,math.min(i+4999,#keys))) end return 1", $args[0], $args[1]) && $cleared;
+ continue;
+ }
+
+ $cursor = null;
+ do {
+ $keys = $host instanceof \Predis\ClientInterface ? $host->scan($cursor, 'MATCH', $pattern, 'COUNT', 1000) : $host->scan($cursor, $pattern, 1000);
+ if (isset($keys[1]) && \is_array($keys[1])) {
+ $cursor = $keys[0];
+ $keys = $keys[1];
+ }
+ if ($keys) {
+ if ($prefixLen) {
+ foreach ($keys as $i => $key) {
+ $keys[$i] = substr($key, $prefixLen);
+ }
+ }
+ $this->doDelete($keys);
+ }
+ } while ($cursor = (int) $cursor);
+ }
+
+ return $cleared;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ if (!$ids) {
+ return true;
+ }
+
+ if ($this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface) {
+ static $del;
+ $del = $del ?? (class_exists(UNLINK::class) ? 'unlink' : 'del');
+
+ $this->pipeline(function () use ($ids, $del) {
+ foreach ($ids as $id) {
+ yield $del => [$id];
+ }
+ })->rewind();
+ } else {
+ static $unlink = true;
+
+ if ($unlink) {
+ try {
+ $unlink = false !== $this->redis->unlink($ids);
+ } catch (\Throwable $e) {
+ $unlink = false;
+ }
+ }
+
+ if (!$unlink) {
+ $this->redis->del($ids);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, int $lifetime)
+ {
+ if (!$values = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ $results = $this->pipeline(function () use ($values, $lifetime) {
+ foreach ($values as $id => $value) {
+ if (0 >= $lifetime) {
+ yield 'set' => [$id, $value];
+ } else {
+ yield 'setEx' => [$id, $lifetime, $value];
+ }
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ if (true !== $result && (!$result instanceof Status || Status::get('OK') !== $result)) {
+ $failed[] = $id;
+ }
+ }
+
+ return $failed;
+ }
+
+ private function pipeline(\Closure $generator, object $redis = null): \Generator
+ {
+ $ids = [];
+ $redis = $redis ?? $this->redis;
+
+ if ($redis instanceof RedisClusterProxy || $redis instanceof \RedisCluster || ($redis instanceof \Predis\ClientInterface && $redis->getConnection() instanceof RedisCluster)) {
+ // phpredis & predis don't support pipelining with RedisCluster
+ // see https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#pipelining
+ // see https://github.com/nrk/predis/issues/267#issuecomment-123781423
+ $results = [];
+ foreach ($generator() as $command => $args) {
+ $results[] = $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? ($redis instanceof \Predis\ClientInterface ? $args[2] : $args[1][0]) : $args[0];
+ }
+ } elseif ($redis instanceof \Predis\ClientInterface) {
+ $results = $redis->pipeline(static function ($redis) use ($generator, &$ids) {
+ foreach ($generator() as $command => $args) {
+ $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? $args[2] : $args[0];
+ }
+ });
+ } elseif ($redis instanceof \RedisArray) {
+ $connections = $results = $ids = [];
+ foreach ($generator() as $command => $args) {
+ $id = 'eval' === $command ? $args[1][0] : $args[0];
+ if (!isset($connections[$h = $redis->_target($id)])) {
+ $connections[$h] = [$redis->_instance($h), -1];
+ $connections[$h][0]->multi(\Redis::PIPELINE);
+ }
+ $connections[$h][0]->{$command}(...$args);
+ $results[] = [$h, ++$connections[$h][1]];
+ $ids[] = $id;
+ }
+ foreach ($connections as $h => $c) {
+ $connections[$h] = $c[0]->exec();
+ }
+ foreach ($results as $k => [$h, $c]) {
+ $results[$k] = $connections[$h][$c];
+ }
+ } else {
+ $redis->multi(\Redis::PIPELINE);
+ foreach ($generator() as $command => $args) {
+ $redis->{$command}(...$args);
+ $ids[] = 'eval' === $command ? $args[1][0] : $args[0];
+ }
+ $results = $redis->exec();
+ }
+
+ if (!$redis instanceof \Predis\ClientInterface && 'eval' === $command && $redis->getLastError()) {
+ $e = new \RedisException($redis->getLastError());
+ $results = array_map(function ($v) use ($e) { return false === $v ? $e : $v; }, $results);
+ }
+
+ foreach ($ids as $k => $id) {
+ yield $id => $results[$k];
+ }
+ }
+
+ private function getHosts(): array
+ {
+ $hosts = [$this->redis];
+ if ($this->redis instanceof \Predis\ClientInterface) {
+ $connection = $this->redis->getConnection();
+ if ($connection instanceof ClusterInterface && $connection instanceof \Traversable) {
+ $hosts = [];
+ foreach ($connection as $c) {
+ $hosts[] = new \Predis\Client($c);
+ }
+ }
+ } elseif ($this->redis instanceof \RedisArray) {
+ $hosts = [];
+ foreach ($this->redis->_hosts() as $host) {
+ $hosts[] = $this->redis->_instance($host);
+ }
+ } elseif ($this->redis instanceof RedisClusterProxy || $this->redis instanceof \RedisCluster) {
+ $hosts = [];
+ foreach ($this->redis->_masters() as $host) {
+ $hosts[] = new RedisClusterNodeProxy($host, $this->redis);
+ }
+ }
+
+ return $hosts;
+ }
+}
diff --git a/vendor/symfony/cache/composer.json b/vendor/symfony/cache/composer.json
new file mode 100644
index 0000000..1ebcfc9
--- /dev/null
+++ b/vendor/symfony/cache/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "symfony/cache",
+ "type": "library",
+ "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation",
+ "keywords": ["caching", "psr6"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "provide": {
+ "psr/cache-implementation": "1.0|2.0",
+ "psr/simple-cache-implementation": "1.0|2.0",
+ "symfony/cache-implementation": "1.0|2.0"
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/cache": "^1.0|^2.0",
+ "psr/log": "^1.1|^2|^3",
+ "symfony/cache-contracts": "^1.1.7|^2",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/polyfill-php73": "^1.9",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/var-exporter": "^4.4|^5.0|^6.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "dev-master",
+ "doctrine/cache": "^1.6|^2.0",
+ "doctrine/dbal": "^2.13.1|^3.0",
+ "predis/predis": "^1.1",
+ "psr/simple-cache": "^1.0|^2.0",
+ "symfony/config": "^4.4|^5.0|^6.0",
+ "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+ "symfony/filesystem": "^4.4|^5.0|^6.0",
+ "symfony/http-kernel": "^4.4|^5.0|^6.0",
+ "symfony/messenger": "^4.4|^5.0|^6.0",
+ "symfony/var-dumper": "^4.4|^5.0|^6.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "<2.13.1",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/var-dumper": "<4.4"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Cache\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/vendor/symfony/config/Builder/ClassBuilder.php b/vendor/symfony/config/Builder/ClassBuilder.php
new file mode 100644
index 0000000..82fadf6
--- /dev/null
+++ b/vendor/symfony/config/Builder/ClassBuilder.php
@@ -0,0 +1,176 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Builder;
+
+/**
+ * Build PHP classes to generate config.
+ *
+ * @internal
+ *
+ * @author Tobias Nyholm
+ */
+class ClassBuilder
+{
+ /** @var string */
+ private $namespace;
+
+ /** @var string */
+ private $name;
+
+ /** @var Property[] */
+ private $properties = [];
+
+ /** @var Method[] */
+ private $methods = [];
+ private $require = [];
+ private $use = [];
+ private $implements = [];
+ private $allowExtraKeys = false;
+
+ public function __construct(string $namespace, string $name)
+ {
+ $this->namespace = $namespace;
+ $this->name = ucfirst($this->camelCase($name)).'Config';
+ }
+
+ public function getDirectory(): string
+ {
+ return str_replace('\\', \DIRECTORY_SEPARATOR, $this->namespace);
+ }
+
+ public function getFilename(): string
+ {
+ return $this->name.'.php';
+ }
+
+ public function build(): string
+ {
+ $rootPath = explode(\DIRECTORY_SEPARATOR, $this->getDirectory());
+ $require = '';
+ foreach ($this->require as $class) {
+ // figure out relative path.
+ $path = explode(\DIRECTORY_SEPARATOR, $class->getDirectory());
+ $path[] = $class->getFilename();
+ foreach ($rootPath as $key => $value) {
+ if ($path[$key] !== $value) {
+ break;
+ }
+ unset($path[$key]);
+ }
+ $require .= sprintf('require_once __DIR__.\DIRECTORY_SEPARATOR.\'%s\';', implode('\'.\DIRECTORY_SEPARATOR.\'', $path))."\n";
+ }
+ $use = '';
+ foreach (array_keys($this->use) as $statement) {
+ $use .= sprintf('use %s;', $statement)."\n";
+ }
+
+ $implements = [] === $this->implements ? '' : 'implements '.implode(', ', $this->implements);
+ $body = '';
+ foreach ($this->properties as $property) {
+ $body .= ' '.$property->getContent()."\n";
+ }
+ foreach ($this->methods as $method) {
+ $lines = explode("\n", $method->getContent());
+ foreach ($lines as $line) {
+ $body .= ' '.$line."\n";
+ }
+ }
+
+ $content = strtr(' $this->namespace, 'REQUIRE' => $require, 'USE' => $use, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]);
+
+ return $content;
+ }
+
+ public function addRequire(self $class): void
+ {
+ $this->require[] = $class;
+ }
+
+ public function addUse(string $class): void
+ {
+ $this->use[$class] = true;
+ }
+
+ public function addImplements(string $interface): void
+ {
+ $this->implements[] = '\\'.ltrim($interface, '\\');
+ }
+
+ public function addMethod(string $name, string $body, array $params = []): void
+ {
+ $this->methods[] = new Method(strtr($body, ['NAME' => $this->camelCase($name)] + $params));
+ }
+
+ public function addProperty(string $name, string $classType = null, string $defaultValue = null): Property
+ {
+ $property = new Property($name, '_' !== $name[0] ? $this->camelCase($name) : $name);
+ if (null !== $classType) {
+ $property->setType($classType);
+ }
+ $this->properties[] = $property;
+ $defaultValue = null !== $defaultValue ? sprintf(' = %s', $defaultValue) : '';
+ $property->setContent(sprintf('private $%s%s;', $property->getName(), $defaultValue));
+
+ return $property;
+ }
+
+ public function getProperties(): array
+ {
+ return $this->properties;
+ }
+
+ private function camelCase(string $input): string
+ {
+ $output = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $input))));
+
+ return preg_replace('#\W#', '', $output);
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getNamespace(): string
+ {
+ return $this->namespace;
+ }
+
+ public function getFqcn(): string
+ {
+ return '\\'.$this->namespace.'\\'.$this->name;
+ }
+
+ public function setAllowExtraKeys(bool $allowExtraKeys): void
+ {
+ $this->allowExtraKeys = $allowExtraKeys;
+ }
+
+ public function shouldAllowExtraKeys(): bool
+ {
+ return $this->allowExtraKeys;
+ }
+}
diff --git a/vendor/symfony/config/Builder/ConfigBuilderGenerator.php b/vendor/symfony/config/Builder/ConfigBuilderGenerator.php
new file mode 100644
index 0000000..920f121
--- /dev/null
+++ b/vendor/symfony/config/Builder/ConfigBuilderGenerator.php
@@ -0,0 +1,470 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Builder;
+
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\BooleanNode;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\EnumNode;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\FloatNode;
+use Symfony\Component\Config\Definition\IntegerNode;
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+use Symfony\Component\Config\Definition\ScalarNode;
+use Symfony\Component\Config\Definition\VariableNode;
+use Symfony\Component\Config\Loader\ParamConfigurator;
+
+/**
+ * Generate ConfigBuilders to help create valid config.
+ *
+ * @author Tobias Nyholm
+ */
+class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface
+{
+ /**
+ * @var ClassBuilder[]
+ */
+ private $classes;
+ private $outputDir;
+
+ public function __construct(string $outputDir)
+ {
+ $this->outputDir = $outputDir;
+ }
+
+ /**
+ * @return \Closure that will return the root config class
+ */
+ public function build(ConfigurationInterface $configuration): \Closure
+ {
+ $this->classes = [];
+
+ $rootNode = $configuration->getConfigTreeBuilder()->buildTree();
+ $rootClass = new ClassBuilder('Symfony\\Config', $rootNode->getName());
+
+ $path = $this->getFullPath($rootClass);
+ if (!is_file($path)) {
+ // Generate the class if the file not exists
+ $this->classes[] = $rootClass;
+ $this->buildNode($rootNode, $rootClass, $this->getSubNamespace($rootClass));
+ $rootClass->addImplements(ConfigBuilderInterface::class);
+ $rootClass->addMethod('getExtensionAlias', '
+public function NAME(): string
+{
+ return \'ALIAS\';
+}', ['ALIAS' => $rootNode->getPath()]);
+
+ $this->writeClasses();
+ }
+
+ $loader = \Closure::fromCallable(function () use ($path, $rootClass) {
+ require_once $path;
+ $className = $rootClass->getFqcn();
+
+ return new $className();
+ });
+
+ return $loader;
+ }
+
+ private function getFullPath(ClassBuilder $class): string
+ {
+ $directory = $this->outputDir.\DIRECTORY_SEPARATOR.$class->getDirectory();
+ if (!is_dir($directory)) {
+ @mkdir($directory, 0777, true);
+ }
+
+ return $directory.\DIRECTORY_SEPARATOR.$class->getFilename();
+ }
+
+ private function writeClasses(): void
+ {
+ foreach ($this->classes as $class) {
+ $this->buildConstructor($class);
+ $this->buildToArray($class);
+ if ($class->getProperties()) {
+ $class->addProperty('_usedProperties', null, '[]');
+ }
+ $this->buildSetExtraKey($class);
+
+ file_put_contents($this->getFullPath($class), $class->build());
+ }
+
+ $this->classes = [];
+ }
+
+ private function buildNode(NodeInterface $node, ClassBuilder $class, string $namespace): void
+ {
+ if (!$node instanceof ArrayNode) {
+ throw new \LogicException('The node was expected to be an ArrayNode. This Configuration includes an edge case not supported yet.');
+ }
+
+ foreach ($node->getChildren() as $child) {
+ switch (true) {
+ case $child instanceof ScalarNode:
+ $this->handleScalarNode($child, $class);
+ break;
+ case $child instanceof PrototypedArrayNode:
+ $this->handlePrototypedArrayNode($child, $class, $namespace);
+ break;
+ case $child instanceof VariableNode:
+ $this->handleVariableNode($child, $class);
+ break;
+ case $child instanceof ArrayNode:
+ $this->handleArrayNode($child, $class, $namespace);
+ break;
+ default:
+ throw new \RuntimeException(sprintf('Unknown node "%s".', \get_class($child)));
+ }
+ }
+ }
+
+ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace): void
+ {
+ $childClass = new ClassBuilder($namespace, $node->getName());
+ $childClass->setAllowExtraKeys($node->shouldIgnoreExtraKeys());
+ $class->addRequire($childClass);
+ $this->classes[] = $childClass;
+
+ $property = $class->addProperty($node->getName(), $childClass->getFqcn());
+ $body = '
+public function NAME(array $value = []): CLASS
+{
+ if (null === $this->PROPERTY) {
+ $this->_usedProperties[\'PROPERTY\'] = true;
+ $this->PROPERTY = new CLASS($value);
+ } elseif ([] !== $value) {
+ throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
+ }
+
+ return $this->PROPERTY;
+}';
+ $class->addUse(InvalidConfigurationException::class);
+ $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
+
+ $this->buildNode($node, $childClass, $this->getSubNamespace($childClass));
+ }
+
+ private function handleVariableNode(VariableNode $node, ClassBuilder $class): void
+ {
+ $comment = $this->getComment($node);
+ $property = $class->addProperty($node->getName());
+ $class->addUse(ParamConfigurator::class);
+
+ $body = '
+/**
+COMMENT * @return $this
+ */
+public function NAME($valueDEFAULT): self
+{
+ $this->_usedProperties[\'PROPERTY\'] = true;
+ $this->PROPERTY = $value;
+
+ return $this;
+}';
+ $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']);
+ }
+
+ private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace): void
+ {
+ $name = $this->getSingularName($node);
+ $prototype = $node->getPrototype();
+ $methodName = $name;
+
+ $parameterType = $this->getParameterType($prototype);
+ if (null !== $parameterType || $prototype instanceof ScalarNode) {
+ $class->addUse(ParamConfigurator::class);
+ $property = $class->addProperty($node->getName());
+ if (null === $key = $node->getKeyAttribute()) {
+ // This is an array of values; don't use singular name
+ $body = '
+/**
+ * @param ParamConfigurator|list $value
+ * @return $this
+ */
+public function NAME($value): self
+{
+ $this->_usedProperties[\'PROPERTY\'] = true;
+ $this->PROPERTY = $value;
+
+ return $this;
+}';
+
+ $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType]);
+ } else {
+ $body = '
+/**
+ * @param ParamConfigurator|TYPE $value
+ * @return $this
+ */
+public function NAME(string $VAR, $VALUE): self
+{
+ $this->_usedProperties[\'PROPERTY\'] = true;
+ $this->PROPERTY[$VAR] = $VALUE;
+
+ return $this;
+}';
+
+ $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType, 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
+ }
+
+ return;
+ }
+
+ $childClass = new ClassBuilder($namespace, $name);
+ if ($prototype instanceof ArrayNode) {
+ $childClass->setAllowExtraKeys($prototype->shouldIgnoreExtraKeys());
+ }
+ $class->addRequire($childClass);
+ $this->classes[] = $childClass;
+ $property = $class->addProperty($node->getName(), $childClass->getFqcn().'[]');
+
+ if (null === $key = $node->getKeyAttribute()) {
+ $body = '
+public function NAME(array $value = []): CLASS
+{
+ $this->_usedProperties[\'PROPERTY\'] = true;
+
+ return $this->PROPERTY[] = new CLASS($value);
+}';
+ $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
+ } else {
+ $body = '
+public function NAME(string $VAR, array $VALUE = []): CLASS
+{
+ if (!isset($this->PROPERTY[$VAR])) {
+ $this->_usedProperties[\'PROPERTY\'] = true;
+
+ return $this->PROPERTY[$VAR] = new CLASS($VALUE);
+ }
+ if ([] === $VALUE) {
+ return $this->PROPERTY[$VAR];
+ }
+
+ throw new InvalidConfigurationException(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\');
+}';
+ $class->addUse(InvalidConfigurationException::class);
+ $class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
+ }
+
+ $this->buildNode($prototype, $childClass, $namespace.'\\'.$childClass->getName());
+ }
+
+ private function handleScalarNode(ScalarNode $node, ClassBuilder $class): void
+ {
+ $comment = $this->getComment($node);
+ $property = $class->addProperty($node->getName());
+ $class->addUse(ParamConfigurator::class);
+
+ $body = '
+/**
+COMMENT * @return $this
+ */
+public function NAME($value): self
+{
+ $this->_usedProperties[\'PROPERTY\'] = true;
+ $this->PROPERTY = $value;
+
+ return $this;
+}';
+
+ $class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
+ }
+
+ private function getParameterType(NodeInterface $node): ?string
+ {
+ if ($node instanceof BooleanNode) {
+ return 'bool';
+ }
+
+ if ($node instanceof IntegerNode) {
+ return 'int';
+ }
+
+ if ($node instanceof FloatNode) {
+ return 'float';
+ }
+
+ if ($node instanceof EnumNode) {
+ return '';
+ }
+
+ if ($node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode) {
+ // This is just an array of variables
+ return 'array';
+ }
+
+ if ($node instanceof VariableNode) {
+ // mixed
+ return '';
+ }
+
+ return null;
+ }
+
+ private function getComment(VariableNode $node): string
+ {
+ $comment = '';
+ if ('' !== $info = (string) $node->getInfo()) {
+ $comment .= ' * '.$info."\n";
+ }
+
+ foreach ((array) ($node->getExample() ?? []) as $example) {
+ $comment .= ' * @example '.$example."\n";
+ }
+
+ if ('' !== $default = $node->getDefaultValue()) {
+ $comment .= ' * @default '.(null === $default ? 'null' : var_export($default, true))."\n";
+ }
+
+ if ($node instanceof EnumNode) {
+ $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_map(function ($a) {
+ return var_export($a, true);
+ }, $node->getValues())))."\n";
+ } else {
+ $parameterType = $this->getParameterType($node);
+ if (null === $parameterType || '' === $parameterType) {
+ $parameterType = 'mixed';
+ }
+ $comment .= ' * @param ParamConfigurator|'.$parameterType.' $value'."\n";
+ }
+
+ if ($node->isDeprecated()) {
+ $comment .= ' * @deprecated '.$node->getDeprecation($node->getName(), $node->getParent()->getName())['message']."\n";
+ }
+
+ return $comment;
+ }
+
+ /**
+ * Pick a good singular name.
+ */
+ private function getSingularName(PrototypedArrayNode $node): string
+ {
+ $name = $node->getName();
+ if ('s' !== substr($name, -1)) {
+ return $name;
+ }
+
+ $parent = $node->getParent();
+ $mappings = $parent instanceof ArrayNode ? $parent->getXmlRemappings() : [];
+ foreach ($mappings as $map) {
+ if ($map[1] === $name) {
+ $name = $map[0];
+ break;
+ }
+ }
+
+ return $name;
+ }
+
+ private function buildToArray(ClassBuilder $class): void
+ {
+ $body = '$output = [];';
+ foreach ($class->getProperties() as $p) {
+ $code = '$this->PROPERTY';
+ if (null !== $p->getType()) {
+ if ($p->isArray()) {
+ $code = 'array_map(function ($v) { return $v->toArray(); }, $this->PROPERTY)';
+ } else {
+ $code = '$this->PROPERTY->toArray()';
+ }
+ }
+
+ $body .= strtr('
+ if (isset($this->_usedProperties[\'PROPERTY\'])) {
+ $output[\'ORG_NAME\'] = '.$code.';
+ }', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
+ }
+
+ $extraKeys = $class->shouldAllowExtraKeys() ? ' + $this->_extraKeys' : '';
+
+ $class->addMethod('toArray', '
+public function NAME(): array
+{
+ '.$body.'
+
+ return $output'.$extraKeys.';
+}');
+ }
+
+ private function buildConstructor(ClassBuilder $class): void
+ {
+ $body = '';
+ foreach ($class->getProperties() as $p) {
+ $code = '$value[\'ORG_NAME\']';
+ if (null !== $p->getType()) {
+ if ($p->isArray()) {
+ $code = 'array_map(function ($v) { return new '.$p->getType().'($v); }, $value[\'ORG_NAME\'])';
+ } else {
+ $code = 'new '.$p->getType().'($value[\'ORG_NAME\'])';
+ }
+ }
+
+ $body .= strtr('
+ if (array_key_exists(\'ORG_NAME\', $value)) {
+ $this->_usedProperties[\'PROPERTY\'] = true;
+ $this->PROPERTY = '.$code.';
+ unset($value[\'ORG_NAME\']);
+ }
+', ['PROPERTY' => $p->getName(), 'ORG_NAME' => $p->getOriginalName()]);
+ }
+
+ if ($class->shouldAllowExtraKeys()) {
+ $body .= '
+ $this->_extraKeys = $value;
+';
+ } else {
+ $body .= '
+ if ([] !== $value) {
+ throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__).implode(\', \', array_keys($value)));
+ }';
+
+ $class->addUse(InvalidConfigurationException::class);
+ }
+
+ $class->addMethod('__construct', '
+public function __construct(array $value = [])
+{
+'.$body.'
+}');
+ }
+
+ private function buildSetExtraKey(ClassBuilder $class): void
+ {
+ if (!$class->shouldAllowExtraKeys()) {
+ return;
+ }
+
+ $class->addUse(ParamConfigurator::class);
+
+ $class->addProperty('_extraKeys');
+
+ $class->addMethod('set', '
+/**
+ * @param ParamConfigurator|mixed $value
+ * @return $this
+ */
+public function NAME(string $key, $value): self
+{
+ $this->_extraKeys[$key] = $value;
+
+ return $this;
+}');
+ }
+
+ private function getSubNamespace(ClassBuilder $rootClass): string
+ {
+ return sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6));
+ }
+}
diff --git a/vendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.php b/vendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.php
new file mode 100644
index 0000000..c52c9e5
--- /dev/null
+++ b/vendor/symfony/config/Builder/ConfigBuilderGeneratorInterface.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\Config\Builder;
+
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+/**
+ * Generates ConfigBuilders to help create valid config.
+ *
+ * @author Tobias Nyholm
+ */
+interface ConfigBuilderGeneratorInterface
+{
+ /**
+ * @return \Closure that will return the root config class
+ */
+ public function build(ConfigurationInterface $configuration): \Closure;
+}
diff --git a/vendor/symfony/config/Builder/ConfigBuilderInterface.php b/vendor/symfony/config/Builder/ConfigBuilderInterface.php
new file mode 100644
index 0000000..fd3129c
--- /dev/null
+++ b/vendor/symfony/config/Builder/ConfigBuilderInterface.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\Config\Builder;
+
+/**
+ * A ConfigBuilder provides helper methods to build a large complex array.
+ *
+ * @author Tobias Nyholm
+ */
+interface ConfigBuilderInterface
+{
+ /**
+ * Gets all configuration represented as an array.
+ */
+ public function toArray(): array;
+
+ /**
+ * Gets the alias for the extension which config we are building.
+ */
+ public function getExtensionAlias(): string;
+}
diff --git a/vendor/symfony/config/Builder/Method.php b/vendor/symfony/config/Builder/Method.php
new file mode 100644
index 0000000..3577e3d
--- /dev/null
+++ b/vendor/symfony/config/Builder/Method.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\Config\Builder;
+
+/**
+ * Represents a method when building classes.
+ *
+ * @internal
+ *
+ * @author Tobias Nyholm
+ */
+class Method
+{
+ private $content;
+
+ public function __construct(string $content)
+ {
+ $this->content = $content;
+ }
+
+ public function getContent(): string
+ {
+ return $this->content;
+ }
+}
diff --git a/vendor/symfony/config/Builder/Property.php b/vendor/symfony/config/Builder/Property.php
new file mode 100644
index 0000000..1b24c47
--- /dev/null
+++ b/vendor/symfony/config/Builder/Property.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Builder;
+
+/**
+ * Represents a property when building classes.
+ *
+ * @internal
+ *
+ * @author Tobias Nyholm
+ */
+class Property
+{
+ private $name;
+ private $originalName;
+ private $array = false;
+ private $type = null;
+ private $content;
+
+ public function __construct(string $originalName, string $name)
+ {
+ $this->name = $name;
+ $this->originalName = $originalName;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getOriginalName(): string
+ {
+ return $this->originalName;
+ }
+
+ public function setType(string $type): void
+ {
+ $this->array = false;
+ $this->type = $type;
+
+ if ('[]' === substr($type, -2)) {
+ $this->array = true;
+ $this->type = substr($type, 0, -2);
+ }
+ }
+
+ public function getType(): ?string
+ {
+ return $this->type;
+ }
+
+ public function getContent(): ?string
+ {
+ return $this->content;
+ }
+
+ public function setContent(string $content): void
+ {
+ $this->content = $content;
+ }
+
+ public function isArray(): bool
+ {
+ return $this->array;
+ }
+}
diff --git a/vendor/symfony/config/CHANGELOG.md b/vendor/symfony/config/CHANGELOG.md
new file mode 100644
index 0000000..75ef8bc
--- /dev/null
+++ b/vendor/symfony/config/CHANGELOG.md
@@ -0,0 +1,129 @@
+CHANGELOG
+=========
+
+5.3.0
+-----
+
+ * Add support for generating `ConfigBuilder` for extensions
+
+5.1.0
+-----
+
+ * updated the signature of method `NodeDefinition::setDeprecated()` to `NodeDefinition::setDeprecation(string $package, string $version, string $message)`
+ * updated the signature of method `BaseNode::setDeprecated()` to `BaseNode::setDeprecation(string $package, string $version, string $message)`
+ * deprecated passing a null message to `BaseNode::setDeprecated()` to un-deprecate a node
+ * deprecated `BaseNode::getDeprecationMessage()`, use `BaseNode::getDeprecation()` instead
+
+5.0.0
+-----
+
+ * Dropped support for constructing a `TreeBuilder` without passing root node information.
+ * Removed the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead
+ * Added method `getChildNodeDefinitions()` to ParentNodeDefinitionInterface
+ * Removed `FileLoaderLoadException`, use `LoaderLoadException` instead
+
+4.4.0
+-----
+
+ * added a way to exclude patterns of resources from being imported by the `import()` method
+
+4.3.0
+-----
+
+ * deprecated using environment variables with `cannotBeEmpty()` if the value is validated with `validate()`
+ * made `Resource\*` classes final and not implement `Serializable` anymore
+ * deprecated the `root()` method in `TreeBuilder`, pass the root node information to the constructor instead
+
+4.2.0
+-----
+
+ * deprecated constructing a `TreeBuilder` without passing root node information
+ * renamed `FileLoaderLoadException` to `LoaderLoadException`
+
+4.1.0
+-----
+
+ * added `setPathSeparator` method to `NodeBuilder` class
+ * added third `$pathSeparator` constructor argument to `BaseNode`
+ * the `Processor` class has been made final
+
+4.0.0
+-----
+
+ * removed `ConfigCachePass`
+
+3.4.0
+-----
+
+ * added `setDeprecated()` method to indicate a deprecated node
+ * added `XmlUtils::parse()` method to parse an XML string
+ * deprecated `ConfigCachePass`
+
+3.3.0
+-----
+
+ * added `ReflectionClassResource` class
+ * added second `$exists` constructor argument to `ClassExistenceResource`
+ * made `ClassExistenceResource` work with interfaces and traits
+ * added `ConfigCachePass` (originally in FrameworkBundle)
+ * added `castToArray()` helper to turn any config value into an array
+
+3.0.0
+-----
+
+ * removed `ReferenceDumper` class
+ * removed the `ResourceInterface::isFresh()` method
+ * removed `BCResourceInterfaceChecker` class
+ * removed `ResourceInterface::getResource()` method
+
+2.8.0
+-----
+
+The edge case of defining just one value for nodes of type Enum is now allowed:
+
+```php
+$rootNode
+ ->children()
+ ->enumNode('variable')
+ ->values(['value'])
+ ->end()
+ ->end()
+;
+```
+
+Before: `InvalidArgumentException` (variable must contain at least two
+distinct elements).
+After: the code will work as expected and it will restrict the values of the
+`variable` option to just `value`.
+
+ * deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they
+ can be validated that way, make them implement the new `SelfCheckingResourceInterface`.
+ * deprecated the getResource() method in ResourceInterface. You can still call this method
+ on concrete classes implementing the interface, but it does not make sense at the interface
+ level as you need to know about the particular type of resource at hand to understand the
+ semantics of the returned value.
+
+2.7.0
+-----
+
+ * added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory`
+ implementation to delegate creation of ConfigCache instances
+
+2.2.0
+-----
+
+ * added `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()`
+ to ease configuration when some sections are respectively disabled / enabled
+ by default.
+ * added a `normalizeKeys()` method for array nodes (to avoid key normalization)
+ * added numerical type handling for config definitions
+ * added convenience methods for optional configuration sections to `ArrayNodeDefinition`
+ * added a utils class for XML manipulations
+
+2.1.0
+-----
+
+ * added a way to add documentation on configuration
+ * implemented `Serializable` on resources
+ * `LoaderResolverInterface` is now used instead of `LoaderResolver` for type
+ hinting
diff --git a/vendor/symfony/config/ConfigCache.php b/vendor/symfony/config/ConfigCache.php
new file mode 100644
index 0000000..3b09052
--- /dev/null
+++ b/vendor/symfony/config/ConfigCache.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
+
+/**
+ * ConfigCache caches arbitrary content in files on disk.
+ *
+ * When in debug mode, those metadata resources that implement
+ * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will
+ * be used to check cache freshness.
+ *
+ * @author Fabien Potencier
+ * @author Matthias Pigulla
+ */
+class ConfigCache extends ResourceCheckerConfigCache
+{
+ private $debug;
+
+ /**
+ * @param string $file The absolute cache path
+ * @param bool $debug Whether debugging is enabled or not
+ */
+ public function __construct(string $file, bool $debug)
+ {
+ $this->debug = $debug;
+
+ $checkers = [];
+ if (true === $this->debug) {
+ $checkers = [new SelfCheckingResourceChecker()];
+ }
+
+ parent::__construct($file, $checkers);
+ }
+
+ /**
+ * Checks if the cache is still fresh.
+ *
+ * This implementation always returns true when debug is off and the
+ * cache file exists.
+ *
+ * @return bool
+ */
+ public function isFresh()
+ {
+ if (!$this->debug && is_file($this->getPath())) {
+ return true;
+ }
+
+ return parent::isFresh();
+ }
+}
diff --git a/vendor/symfony/config/ConfigCacheFactory.php b/vendor/symfony/config/ConfigCacheFactory.php
new file mode 100644
index 0000000..11fd3cb
--- /dev/null
+++ b/vendor/symfony/config/ConfigCacheFactory.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+/**
+ * Basic implementation of ConfigCacheFactoryInterface that
+ * creates an instance of the default ConfigCache.
+ *
+ * This factory and/or cache do not support cache validation
+ * by means of ResourceChecker instances (that is, service-based).
+ *
+ * @author Matthias Pigulla
+ */
+class ConfigCacheFactory implements ConfigCacheFactoryInterface
+{
+ private $debug;
+
+ /**
+ * @param bool $debug The debug flag to pass to ConfigCache
+ */
+ public function __construct(bool $debug)
+ {
+ $this->debug = $debug;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cache(string $file, callable $callback)
+ {
+ $cache = new ConfigCache($file, $this->debug);
+ if (!$cache->isFresh()) {
+ $callback($cache);
+ }
+
+ return $cache;
+ }
+}
diff --git a/vendor/symfony/config/ConfigCacheFactoryInterface.php b/vendor/symfony/config/ConfigCacheFactoryInterface.php
new file mode 100644
index 0000000..146ee9b
--- /dev/null
+++ b/vendor/symfony/config/ConfigCacheFactoryInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+/**
+ * Interface for a ConfigCache factory. This factory creates
+ * an instance of ConfigCacheInterface and initializes the
+ * cache if necessary.
+ *
+ * @author Matthias Pigulla
+ */
+interface ConfigCacheFactoryInterface
+{
+ /**
+ * Creates a cache instance and (re-)initializes it if necessary.
+ *
+ * @param string $file The absolute cache file path
+ * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
+ *
+ * @return ConfigCacheInterface
+ */
+ public function cache(string $file, callable $callable);
+}
diff --git a/vendor/symfony/config/ConfigCacheInterface.php b/vendor/symfony/config/ConfigCacheInterface.php
new file mode 100644
index 0000000..3cd7a5c
--- /dev/null
+++ b/vendor/symfony/config/ConfigCacheInterface.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * Interface for ConfigCache.
+ *
+ * @author Matthias Pigulla
+ */
+interface ConfigCacheInterface
+{
+ /**
+ * Gets the cache file path.
+ *
+ * @return string
+ */
+ public function getPath();
+
+ /**
+ * Checks if the cache is still fresh.
+ *
+ * This check should take the metadata passed to the write() method into consideration.
+ *
+ * @return bool
+ */
+ public function isFresh();
+
+ /**
+ * Writes the given content into the cache file. Metadata will be stored
+ * independently and can be used to check cache freshness at a later time.
+ *
+ * @param string $content The content to write into the cache
+ * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
+ *
+ * @throws \RuntimeException When the cache file cannot be written
+ */
+ public function write(string $content, array $metadata = null);
+}
diff --git a/vendor/symfony/config/Definition/ArrayNode.php b/vendor/symfony/config/Definition/ArrayNode.php
new file mode 100644
index 0000000..bd0eae9
--- /dev/null
+++ b/vendor/symfony/config/Definition/ArrayNode.php
@@ -0,0 +1,404 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+
+/**
+ * Represents an Array node in the config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ArrayNode extends BaseNode implements PrototypeNodeInterface
+{
+ protected $xmlRemappings = [];
+ protected $children = [];
+ protected $allowFalse = false;
+ protected $allowNewKeys = true;
+ protected $addIfNotSet = false;
+ protected $performDeepMerging = true;
+ protected $ignoreExtraKeys = false;
+ protected $removeExtraKeys = true;
+ protected $normalizeKeys = true;
+
+ public function setNormalizeKeys(bool $normalizeKeys)
+ {
+ $this->normalizeKeys = $normalizeKeys;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
+ * After running this method, all keys are normalized to foo_bar.
+ *
+ * If you have a mixed key like foo-bar_moo, it will not be altered.
+ * The key will also not be altered if the target key already exists.
+ */
+ protected function preNormalize($value)
+ {
+ if (!$this->normalizeKeys || !\is_array($value)) {
+ return $value;
+ }
+
+ $normalized = [];
+
+ foreach ($value as $k => $v) {
+ if (str_contains($k, '-') && !str_contains($k, '_') && !\array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) {
+ $normalized[$normalizedKey] = $v;
+ } else {
+ $normalized[$k] = $v;
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Retrieves the children of this node.
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Sets the xml remappings that should be performed.
+ *
+ * @param array $remappings An array of the form [[string, string]]
+ */
+ public function setXmlRemappings(array $remappings)
+ {
+ $this->xmlRemappings = $remappings;
+ }
+
+ /**
+ * Gets the xml remappings that should be performed.
+ *
+ * @return array an array of the form [[string, string]]
+ */
+ public function getXmlRemappings()
+ {
+ return $this->xmlRemappings;
+ }
+
+ /**
+ * Sets whether to add default values for this array if it has not been
+ * defined in any of the configuration files.
+ */
+ public function setAddIfNotSet(bool $boolean)
+ {
+ $this->addIfNotSet = $boolean;
+ }
+
+ /**
+ * Sets whether false is allowed as value indicating that the array should be unset.
+ */
+ public function setAllowFalse(bool $allow)
+ {
+ $this->allowFalse = $allow;
+ }
+
+ /**
+ * Sets whether new keys can be defined in subsequent configurations.
+ */
+ public function setAllowNewKeys(bool $allow)
+ {
+ $this->allowNewKeys = $allow;
+ }
+
+ /**
+ * Sets if deep merging should occur.
+ */
+ public function setPerformDeepMerging(bool $boolean)
+ {
+ $this->performDeepMerging = $boolean;
+ }
+
+ /**
+ * Whether extra keys should just be ignored without an exception.
+ *
+ * @param bool $boolean To allow extra keys
+ * @param bool $remove To remove extra keys
+ */
+ public function setIgnoreExtraKeys(bool $boolean, bool $remove = true)
+ {
+ $this->ignoreExtraKeys = $boolean;
+ $this->removeExtraKeys = $this->ignoreExtraKeys && $remove;
+ }
+
+ /**
+ * Returns true when extra keys should be ignored without an exception.
+ */
+ public function shouldIgnoreExtraKeys(): bool
+ {
+ return $this->ignoreExtraKeys;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDefaultValue()
+ {
+ return $this->addIfNotSet;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultValue()
+ {
+ if (!$this->hasDefaultValue()) {
+ throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
+ }
+
+ $defaults = [];
+ foreach ($this->children as $name => $child) {
+ if ($child->hasDefaultValue()) {
+ $defaults[$name] = $child->getDefaultValue();
+ }
+ }
+
+ return $defaults;
+ }
+
+ /**
+ * Adds a child node.
+ *
+ * @throws \InvalidArgumentException when the child node has no name
+ * @throws \InvalidArgumentException when the child node's name is not unique
+ */
+ public function addChild(NodeInterface $node)
+ {
+ $name = $node->getName();
+ if ('' === $name) {
+ throw new \InvalidArgumentException('Child nodes must be named.');
+ }
+ if (isset($this->children[$name])) {
+ throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name));
+ }
+
+ $this->children[$name] = $node;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws UnsetKeyException
+ * @throws InvalidConfigurationException if the node doesn't have enough children
+ */
+ protected function finalizeValue($value)
+ {
+ if (false === $value) {
+ throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value)));
+ }
+
+ foreach ($this->children as $name => $child) {
+ if (!\array_key_exists($name, $value)) {
+ if ($child->isRequired()) {
+ $message = sprintf('The child config "%s" under "%s" must be configured', $name, $this->getPath());
+ if ($child->getInfo()) {
+ $message .= sprintf(': %s', $child->getInfo());
+ } else {
+ $message .= '.';
+ }
+ $ex = new InvalidConfigurationException($message);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ if ($child->hasDefaultValue()) {
+ $value[$name] = $child->getDefaultValue();
+ }
+
+ continue;
+ }
+
+ if ($child->isDeprecated()) {
+ $deprecation = $child->getDeprecation($name, $this->getPath());
+ trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']);
+ }
+
+ try {
+ $value[$name] = $child->finalize($value[$name]);
+ } catch (UnsetKeyException $e) {
+ unset($value[$name]);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!\is_array($value) && (!$this->allowFalse || false !== $value)) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidConfigurationException
+ */
+ protected function normalizeValue($value)
+ {
+ if (false === $value) {
+ return $value;
+ }
+
+ $value = $this->remapXml($value);
+
+ $normalized = [];
+ foreach ($value as $name => $val) {
+ if (isset($this->children[$name])) {
+ try {
+ $normalized[$name] = $this->children[$name]->normalize($val);
+ } catch (UnsetKeyException $e) {
+ }
+ unset($value[$name]);
+ } elseif (!$this->removeExtraKeys) {
+ $normalized[$name] = $val;
+ }
+ }
+
+ // if extra fields are present, throw exception
+ if (\count($value) && !$this->ignoreExtraKeys) {
+ $proposals = array_keys($this->children);
+ sort($proposals);
+ $guesses = [];
+
+ foreach (array_keys($value) as $subject) {
+ $minScore = \INF;
+ foreach ($proposals as $proposal) {
+ $distance = levenshtein($subject, $proposal);
+ if ($distance <= $minScore && $distance < 3) {
+ $guesses[$proposal] = $distance;
+ $minScore = $distance;
+ }
+ }
+ }
+
+ $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath());
+
+ if (\count($guesses)) {
+ asort($guesses);
+ $msg .= sprintf('. Did you mean "%s"?', implode('", "', array_keys($guesses)));
+ } else {
+ $msg .= sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', implode('", "', $proposals));
+ }
+
+ $ex = new InvalidConfigurationException($msg);
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Remaps multiple singular values to a single plural value.
+ *
+ * @return array
+ */
+ protected function remapXml(array $value)
+ {
+ foreach ($this->xmlRemappings as [$singular, $plural]) {
+ if (!isset($value[$singular])) {
+ continue;
+ }
+
+ $value[$plural] = Processor::normalizeConfig($value, $singular, $plural);
+ unset($value[$singular]);
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidConfigurationException
+ * @throws \RuntimeException
+ */
+ protected function mergeValues($leftSide, $rightSide)
+ {
+ if (false === $rightSide) {
+ // if this is still false after the last config has been merged the
+ // finalization pass will take care of removing this key entirely
+ return false;
+ }
+
+ if (false === $leftSide || !$this->performDeepMerging) {
+ return $rightSide;
+ }
+
+ foreach ($rightSide as $k => $v) {
+ // no conflict
+ if (!\array_key_exists($k, $leftSide)) {
+ if (!$this->allowNewKeys) {
+ $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath()));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ $leftSide[$k] = $v;
+ continue;
+ }
+
+ if (!isset($this->children[$k])) {
+ if (!$this->ignoreExtraKeys || $this->removeExtraKeys) {
+ throw new \RuntimeException('merge() expects a normalized config array.');
+ }
+
+ $leftSide[$k] = $v;
+ continue;
+ }
+
+ $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
+ }
+
+ return $leftSide;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function allowPlaceholders(): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/symfony/config/Definition/BaseNode.php b/vendor/symfony/config/Definition/BaseNode.php
new file mode 100644
index 0000000..5ca1239
--- /dev/null
+++ b/vendor/symfony/config/Definition/BaseNode.php
@@ -0,0 +1,589 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\Exception;
+use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+
+/**
+ * The base node class.
+ *
+ * @author Johannes M. Schmitt
+ */
+abstract class BaseNode implements NodeInterface
+{
+ public const DEFAULT_PATH_SEPARATOR = '.';
+
+ private static $placeholderUniquePrefixes = [];
+ private static $placeholders = [];
+
+ protected $name;
+ protected $parent;
+ protected $normalizationClosures = [];
+ protected $finalValidationClosures = [];
+ protected $allowOverwrite = true;
+ protected $required = false;
+ protected $deprecation = [];
+ protected $equivalentValues = [];
+ protected $attributes = [];
+ protected $pathSeparator;
+
+ private $handlingPlaceholder;
+
+ /**
+ * @throws \InvalidArgumentException if the name contains a period
+ */
+ public function __construct(?string $name, NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR)
+ {
+ if (str_contains($name = (string) $name, $pathSeparator)) {
+ throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".');
+ }
+
+ $this->name = $name;
+ $this->parent = $parent;
+ $this->pathSeparator = $pathSeparator;
+ }
+
+ /**
+ * Register possible (dummy) values for a dynamic placeholder value.
+ *
+ * Matching configuration values will be processed with a provided value, one by one. After a provided value is
+ * successfully processed the configuration value is returned as is, thus preserving the placeholder.
+ *
+ * @internal
+ */
+ public static function setPlaceholder(string $placeholder, array $values): void
+ {
+ if (!$values) {
+ throw new \InvalidArgumentException('At least one value must be provided.');
+ }
+
+ self::$placeholders[$placeholder] = $values;
+ }
+
+ /**
+ * Adds a common prefix for dynamic placeholder values.
+ *
+ * Matching configuration values will be skipped from being processed and are returned as is, thus preserving the
+ * placeholder. An exact match provided by {@see setPlaceholder()} might take precedence.
+ *
+ * @internal
+ */
+ public static function setPlaceholderUniquePrefix(string $prefix): void
+ {
+ self::$placeholderUniquePrefixes[] = $prefix;
+ }
+
+ /**
+ * Resets all current placeholders available.
+ *
+ * @internal
+ */
+ public static function resetPlaceholders(): void
+ {
+ self::$placeholderUniquePrefixes = [];
+ self::$placeholders = [];
+ }
+
+ public function setAttribute(string $key, $value)
+ {
+ $this->attributes[$key] = $value;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getAttribute(string $key, $default = null)
+ {
+ return $this->attributes[$key] ?? $default;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasAttribute(string $key)
+ {
+ return isset($this->attributes[$key]);
+ }
+
+ /**
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+ }
+
+ public function removeAttribute(string $key)
+ {
+ unset($this->attributes[$key]);
+ }
+
+ /**
+ * Sets an info message.
+ */
+ public function setInfo(string $info)
+ {
+ $this->setAttribute('info', $info);
+ }
+
+ /**
+ * Returns info message.
+ *
+ * @return string|null
+ */
+ public function getInfo()
+ {
+ return $this->getAttribute('info');
+ }
+
+ /**
+ * Sets the example configuration for this node.
+ *
+ * @param string|array $example
+ */
+ public function setExample($example)
+ {
+ $this->setAttribute('example', $example);
+ }
+
+ /**
+ * Retrieves the example configuration for this node.
+ *
+ * @return string|array|null
+ */
+ public function getExample()
+ {
+ return $this->getAttribute('example');
+ }
+
+ /**
+ * Adds an equivalent value.
+ *
+ * @param mixed $originalValue
+ * @param mixed $equivalentValue
+ */
+ public function addEquivalentValue($originalValue, $equivalentValue)
+ {
+ $this->equivalentValues[] = [$originalValue, $equivalentValue];
+ }
+
+ /**
+ * Set this node as required.
+ */
+ public function setRequired(bool $boolean)
+ {
+ $this->required = $boolean;
+ }
+
+ /**
+ * Sets this node as deprecated.
+ *
+ * @param string $package The name of the composer package that is triggering the deprecation
+ * @param string $version The version of the package that introduced the deprecation
+ * @param string $message the deprecation message to use
+ *
+ * You can use %node% and %path% placeholders in your message to display,
+ * respectively, the node name and its complete path
+ */
+ public function setDeprecated(?string $package/*, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */)
+ {
+ $args = \func_get_args();
+
+ if (\func_num_args() < 2) {
+ trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
+
+ if (!isset($args[0])) {
+ trigger_deprecation('symfony/config', '5.1', 'Passing a null message to un-deprecate a node is deprecated.');
+
+ $this->deprecation = [];
+
+ return;
+ }
+
+ $message = (string) $args[0];
+ $package = $version = '';
+ } else {
+ $package = (string) $args[0];
+ $version = (string) $args[1];
+ $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.');
+ }
+
+ $this->deprecation = [
+ 'package' => $package,
+ 'version' => $version,
+ 'message' => $message,
+ ];
+ }
+
+ /**
+ * Sets if this node can be overridden.
+ */
+ public function setAllowOverwrite(bool $allow)
+ {
+ $this->allowOverwrite = $allow;
+ }
+
+ /**
+ * Sets the closures used for normalization.
+ *
+ * @param \Closure[] $closures An array of Closures used for normalization
+ */
+ public function setNormalizationClosures(array $closures)
+ {
+ $this->normalizationClosures = $closures;
+ }
+
+ /**
+ * Sets the closures used for final validation.
+ *
+ * @param \Closure[] $closures An array of Closures used for final validation
+ */
+ public function setFinalValidationClosures(array $closures)
+ {
+ $this->finalValidationClosures = $closures;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isRequired()
+ {
+ return $this->required;
+ }
+
+ /**
+ * Checks if this node is deprecated.
+ *
+ * @return bool
+ */
+ public function isDeprecated()
+ {
+ return (bool) $this->deprecation;
+ }
+
+ /**
+ * Returns the deprecated message.
+ *
+ * @param string $node the configuration node name
+ * @param string $path the path of the node
+ *
+ * @return string
+ *
+ * @deprecated since Symfony 5.1, use "getDeprecation()" instead.
+ */
+ public function getDeprecationMessage(string $node, string $path)
+ {
+ trigger_deprecation('symfony/config', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);
+
+ return $this->getDeprecation($node, $path)['message'];
+ }
+
+ /**
+ * @param string $node The configuration node name
+ * @param string $path The path of the node
+ */
+ public function getDeprecation(string $node, string $path): array
+ {
+ return [
+ 'package' => $this->deprecation['package'] ?? '',
+ 'version' => $this->deprecation['version'] ?? '',
+ 'message' => strtr($this->deprecation['message'] ?? '', ['%node%' => $node, '%path%' => $path]),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPath()
+ {
+ if (null !== $this->parent) {
+ return $this->parent->getPath().$this->pathSeparator.$this->name;
+ }
+
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ final public function merge($leftSide, $rightSide)
+ {
+ if (!$this->allowOverwrite) {
+ throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath()));
+ }
+
+ if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) {
+ foreach ($leftPlaceholders as $leftPlaceholder) {
+ $this->handlingPlaceholder = $leftSide;
+ try {
+ $this->merge($leftPlaceholder, $rightSide);
+ } finally {
+ $this->handlingPlaceholder = null;
+ }
+ }
+
+ return $rightSide;
+ }
+
+ if ($rightSide !== $rightPlaceholders = self::resolvePlaceholderValue($rightSide)) {
+ foreach ($rightPlaceholders as $rightPlaceholder) {
+ $this->handlingPlaceholder = $rightSide;
+ try {
+ $this->merge($leftSide, $rightPlaceholder);
+ } finally {
+ $this->handlingPlaceholder = null;
+ }
+ }
+
+ return $rightSide;
+ }
+
+ $this->doValidateType($leftSide);
+ $this->doValidateType($rightSide);
+
+ return $this->mergeValues($leftSide, $rightSide);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ final public function normalize($value)
+ {
+ $value = $this->preNormalize($value);
+
+ // run custom normalization closures
+ foreach ($this->normalizationClosures as $closure) {
+ $value = $closure($value);
+ }
+
+ // resolve placeholder value
+ if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
+ foreach ($placeholders as $placeholder) {
+ $this->handlingPlaceholder = $value;
+ try {
+ $this->normalize($placeholder);
+ } finally {
+ $this->handlingPlaceholder = null;
+ }
+ }
+
+ return $value;
+ }
+
+ // replace value with their equivalent
+ foreach ($this->equivalentValues as $data) {
+ if ($data[0] === $value) {
+ $value = $data[1];
+ }
+ }
+
+ // validate type
+ $this->doValidateType($value);
+
+ // normalize value
+ return $this->normalizeValue($value);
+ }
+
+ /**
+ * Normalizes the value before any other normalization is applied.
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ protected function preNormalize($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Returns parent node for this node.
+ *
+ * @return NodeInterface|null
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ final public function finalize($value)
+ {
+ if ($value !== $placeholders = self::resolvePlaceholderValue($value)) {
+ foreach ($placeholders as $placeholder) {
+ $this->handlingPlaceholder = $value;
+ try {
+ $this->finalize($placeholder);
+ } finally {
+ $this->handlingPlaceholder = null;
+ }
+ }
+
+ return $value;
+ }
+
+ $this->doValidateType($value);
+
+ $value = $this->finalizeValue($value);
+
+ // Perform validation on the final value if a closure has been set.
+ // The closure is also allowed to return another value.
+ foreach ($this->finalValidationClosures as $closure) {
+ try {
+ $value = $closure($value);
+ } catch (Exception $e) {
+ if ($e instanceof UnsetKeyException && null !== $this->handlingPlaceholder) {
+ continue;
+ }
+
+ throw $e;
+ } catch (\Exception $e) {
+ throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": ', $this->getPath()).$e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Validates the type of a Node.
+ *
+ * @param mixed $value The value to validate
+ *
+ * @throws InvalidTypeException when the value is invalid
+ */
+ abstract protected function validateType($value);
+
+ /**
+ * Normalizes the value.
+ *
+ * @param mixed $value The value to normalize
+ *
+ * @return mixed
+ */
+ abstract protected function normalizeValue($value);
+
+ /**
+ * Merges two values together.
+ *
+ * @param mixed $leftSide
+ * @param mixed $rightSide
+ *
+ * @return mixed
+ */
+ abstract protected function mergeValues($leftSide, $rightSide);
+
+ /**
+ * Finalizes a value.
+ *
+ * @param mixed $value The value to finalize
+ *
+ * @return mixed
+ */
+ abstract protected function finalizeValue($value);
+
+ /**
+ * Tests if placeholder values are allowed for this node.
+ */
+ protected function allowPlaceholders(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Tests if a placeholder is being handled currently.
+ */
+ protected function isHandlingPlaceholder(): bool
+ {
+ return null !== $this->handlingPlaceholder;
+ }
+
+ /**
+ * Gets allowed dynamic types for this node.
+ */
+ protected function getValidPlaceholderTypes(): array
+ {
+ return [];
+ }
+
+ private static function resolvePlaceholderValue($value)
+ {
+ if (\is_string($value)) {
+ if (isset(self::$placeholders[$value])) {
+ return self::$placeholders[$value];
+ }
+
+ foreach (self::$placeholderUniquePrefixes as $placeholderUniquePrefix) {
+ if (str_starts_with($value, $placeholderUniquePrefix)) {
+ return [];
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ private function doValidateType($value): void
+ {
+ if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) {
+ $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath()));
+ $e->setPath($this->getPath());
+
+ throw $e;
+ }
+
+ if (null === $this->handlingPlaceholder || null === $value) {
+ $this->validateType($value);
+
+ return;
+ }
+
+ $knownTypes = array_keys(self::$placeholders[$this->handlingPlaceholder]);
+ $validTypes = $this->getValidPlaceholderTypes();
+
+ if ($validTypes && array_diff($knownTypes, $validTypes)) {
+ $e = new InvalidTypeException(sprintf(
+ 'Invalid type for path "%s". Expected %s, but got %s.',
+ $this->getPath(),
+ 1 === \count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"',
+ 1 === \count($knownTypes) ? '"'.reset($knownTypes).'"' : 'one of "'.implode('", "', $knownTypes).'"'
+ ));
+ if ($hint = $this->getInfo()) {
+ $e->addHint($hint);
+ }
+ $e->setPath($this->getPath());
+
+ throw $e;
+ }
+
+ $this->validateType($value);
+ }
+}
diff --git a/vendor/symfony/config/Definition/BooleanNode.php b/vendor/symfony/config/Definition/BooleanNode.php
new file mode 100644
index 0000000..c64ecb8
--- /dev/null
+++ b/vendor/symfony/config/Definition/BooleanNode.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents a Boolean value in the config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class BooleanNode extends ScalarNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!\is_bool($value)) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isValueEmpty($value)
+ {
+ // a boolean value cannot be empty
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getValidPlaceholderTypes(): array
+ {
+ return ['bool'];
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php
new file mode 100644
index 0000000..eb5b040
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php
@@ -0,0 +1,549 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+
+/**
+ * This class provides a fluent interface for defining an array node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface
+{
+ protected $performDeepMerging = true;
+ protected $ignoreExtraKeys = false;
+ protected $removeExtraKeys = true;
+ protected $children = [];
+ protected $prototype;
+ protected $atLeastOne = false;
+ protected $allowNewKeys = true;
+ protected $key;
+ protected $removeKeyItem;
+ protected $addDefaults = false;
+ protected $addDefaultChildren = false;
+ protected $nodeBuilder;
+ protected $normalizeKeys = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(?string $name, NodeParentInterface $parent = null)
+ {
+ parent::__construct($name, $parent);
+
+ $this->nullEquivalent = [];
+ $this->trueEquivalent = [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setBuilder(NodeBuilder $builder)
+ {
+ $this->nodeBuilder = $builder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function children()
+ {
+ return $this->getNodeBuilder();
+ }
+
+ /**
+ * Sets a prototype for child nodes.
+ *
+ * @return NodeDefinition
+ */
+ public function prototype(string $type)
+ {
+ return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this);
+ }
+
+ /**
+ * @return VariableNodeDefinition
+ */
+ public function variablePrototype()
+ {
+ return $this->prototype('variable');
+ }
+
+ /**
+ * @return ScalarNodeDefinition
+ */
+ public function scalarPrototype()
+ {
+ return $this->prototype('scalar');
+ }
+
+ /**
+ * @return BooleanNodeDefinition
+ */
+ public function booleanPrototype()
+ {
+ return $this->prototype('boolean');
+ }
+
+ /**
+ * @return IntegerNodeDefinition
+ */
+ public function integerPrototype()
+ {
+ return $this->prototype('integer');
+ }
+
+ /**
+ * @return FloatNodeDefinition
+ */
+ public function floatPrototype()
+ {
+ return $this->prototype('float');
+ }
+
+ /**
+ * @return ArrayNodeDefinition
+ */
+ public function arrayPrototype()
+ {
+ return $this->prototype('array');
+ }
+
+ /**
+ * @return EnumNodeDefinition
+ */
+ public function enumPrototype()
+ {
+ return $this->prototype('enum');
+ }
+
+ /**
+ * Adds the default value if the node is not set in the configuration.
+ *
+ * This method is applicable to concrete nodes only (not to prototype nodes).
+ * If this function has been called and the node is not set during the finalization
+ * phase, it's default value will be derived from its children default values.
+ *
+ * @return $this
+ */
+ public function addDefaultsIfNotSet()
+ {
+ $this->addDefaults = true;
+
+ return $this;
+ }
+
+ /**
+ * Adds children with a default value when none are defined.
+ *
+ * This method is applicable to prototype nodes only.
+ *
+ * @param int|string|array|null $children The number of children|The child name|The children names to be added
+ *
+ * @return $this
+ */
+ public function addDefaultChildrenIfNoneSet($children = null)
+ {
+ $this->addDefaultChildren = $children;
+
+ return $this;
+ }
+
+ /**
+ * Requires the node to have at least one element.
+ *
+ * This method is applicable to prototype nodes only.
+ *
+ * @return $this
+ */
+ public function requiresAtLeastOneElement()
+ {
+ $this->atLeastOne = true;
+
+ return $this;
+ }
+
+ /**
+ * Disallows adding news keys in a subsequent configuration.
+ *
+ * If used all keys have to be defined in the same configuration file.
+ *
+ * @return $this
+ */
+ public function disallowNewKeysInSubsequentConfigs()
+ {
+ $this->allowNewKeys = false;
+
+ return $this;
+ }
+
+ /**
+ * Sets a normalization rule for XML configurations.
+ *
+ * @param string $singular The key to remap
+ * @param string|null $plural The plural of the key for irregular plurals
+ *
+ * @return $this
+ */
+ public function fixXmlConfig(string $singular, string $plural = null)
+ {
+ $this->normalization()->remap($singular, $plural);
+
+ return $this;
+ }
+
+ /**
+ * Sets the attribute which value is to be used as key.
+ *
+ * This is useful when you have an indexed array that should be an
+ * associative array. You can select an item from within the array
+ * to be the key of the particular item. For example, if "id" is the
+ * "key", then:
+ *
+ * [
+ * ['id' => 'my_name', 'foo' => 'bar'],
+ * ];
+ *
+ * becomes
+ *
+ * [
+ * 'my_name' => ['foo' => 'bar'],
+ * ];
+ *
+ * If you'd like "'id' => 'my_name'" to still be present in the resulting
+ * array, then you can set the second argument of this method to false.
+ *
+ * This method is applicable to prototype nodes only.
+ *
+ * @param string $name The name of the key
+ * @param bool $removeKeyItem Whether or not the key item should be removed
+ *
+ * @return $this
+ */
+ public function useAttributeAsKey(string $name, bool $removeKeyItem = true)
+ {
+ $this->key = $name;
+ $this->removeKeyItem = $removeKeyItem;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether the node can be unset.
+ *
+ * @return $this
+ */
+ public function canBeUnset(bool $allow = true)
+ {
+ $this->merge()->allowUnset($allow);
+
+ return $this;
+ }
+
+ /**
+ * Adds an "enabled" boolean to enable the current section.
+ *
+ * By default, the section is disabled. If any configuration is specified then
+ * the node will be automatically enabled:
+ *
+ * enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden
+ * enableableArrayNode: ~ # The config is enabled & use the default values
+ * enableableArrayNode: true # The config is enabled & use the default values
+ * enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden
+ * enableableArrayNode: {enabled: false, ...} # The config is disabled
+ * enableableArrayNode: false # The config is disabled
+ *
+ * @return $this
+ */
+ public function canBeEnabled()
+ {
+ $this
+ ->addDefaultsIfNotSet()
+ ->treatFalseLike(['enabled' => false])
+ ->treatTrueLike(['enabled' => true])
+ ->treatNullLike(['enabled' => true])
+ ->beforeNormalization()
+ ->ifArray()
+ ->then(function (array $v) {
+ $v['enabled'] = $v['enabled'] ?? true;
+
+ return $v;
+ })
+ ->end()
+ ->children()
+ ->booleanNode('enabled')
+ ->defaultFalse()
+ ;
+
+ return $this;
+ }
+
+ /**
+ * Adds an "enabled" boolean to enable the current section.
+ *
+ * By default, the section is enabled.
+ *
+ * @return $this
+ */
+ public function canBeDisabled()
+ {
+ $this
+ ->addDefaultsIfNotSet()
+ ->treatFalseLike(['enabled' => false])
+ ->treatTrueLike(['enabled' => true])
+ ->treatNullLike(['enabled' => true])
+ ->children()
+ ->booleanNode('enabled')
+ ->defaultTrue()
+ ;
+
+ return $this;
+ }
+
+ /**
+ * Disables the deep merging of the node.
+ *
+ * @return $this
+ */
+ public function performNoDeepMerging()
+ {
+ $this->performDeepMerging = false;
+
+ return $this;
+ }
+
+ /**
+ * Allows extra config keys to be specified under an array without
+ * throwing an exception.
+ *
+ * Those config values are ignored and removed from the resulting
+ * array. This should be used only in special cases where you want
+ * to send an entire configuration array through a special tree that
+ * processes only part of the array.
+ *
+ * @param bool $remove Whether to remove the extra keys
+ *
+ * @return $this
+ */
+ public function ignoreExtraKeys(bool $remove = true)
+ {
+ $this->ignoreExtraKeys = true;
+ $this->removeExtraKeys = $remove;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether to enable key normalization.
+ *
+ * @return $this
+ */
+ public function normalizeKeys(bool $bool)
+ {
+ $this->normalizeKeys = $bool;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function append(NodeDefinition $node)
+ {
+ $this->children[$node->name] = $node->setParent($this);
+
+ return $this;
+ }
+
+ /**
+ * Returns a node builder to be used to add children and prototype.
+ *
+ * @return NodeBuilder
+ */
+ protected function getNodeBuilder()
+ {
+ if (null === $this->nodeBuilder) {
+ $this->nodeBuilder = new NodeBuilder();
+ }
+
+ return $this->nodeBuilder->setParent($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function createNode()
+ {
+ if (null === $this->prototype) {
+ $node = new ArrayNode($this->name, $this->parent, $this->pathSeparator);
+
+ $this->validateConcreteNode($node);
+
+ $node->setAddIfNotSet($this->addDefaults);
+
+ foreach ($this->children as $child) {
+ $child->parent = $node;
+ $node->addChild($child->getNode());
+ }
+ } else {
+ $node = new PrototypedArrayNode($this->name, $this->parent, $this->pathSeparator);
+
+ $this->validatePrototypeNode($node);
+
+ if (null !== $this->key) {
+ $node->setKeyAttribute($this->key, $this->removeKeyItem);
+ }
+
+ if (true === $this->atLeastOne || false === $this->allowEmptyValue) {
+ $node->setMinNumberOfElements(1);
+ }
+
+ if ($this->default) {
+ if (!\is_array($this->defaultValue)) {
+ throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.', $node->getPath()));
+ }
+
+ $node->setDefaultValue($this->defaultValue);
+ }
+
+ if (false !== $this->addDefaultChildren) {
+ $node->setAddChildrenIfNoneSet($this->addDefaultChildren);
+ if ($this->prototype instanceof static && null === $this->prototype->prototype) {
+ $this->prototype->addDefaultsIfNotSet();
+ }
+ }
+
+ $this->prototype->parent = $node;
+ $node->setPrototype($this->prototype->getNode());
+ }
+
+ $node->setAllowNewKeys($this->allowNewKeys);
+ $node->addEquivalentValue(null, $this->nullEquivalent);
+ $node->addEquivalentValue(true, $this->trueEquivalent);
+ $node->addEquivalentValue(false, $this->falseEquivalent);
+ $node->setPerformDeepMerging($this->performDeepMerging);
+ $node->setRequired($this->required);
+ $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys);
+ $node->setNormalizeKeys($this->normalizeKeys);
+
+ if ($this->deprecation) {
+ $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']);
+ }
+
+ if (null !== $this->normalization) {
+ $node->setNormalizationClosures($this->normalization->before);
+ $node->setXmlRemappings($this->normalization->remappings);
+ }
+
+ if (null !== $this->merge) {
+ $node->setAllowOverwrite($this->merge->allowOverwrite);
+ $node->setAllowFalse($this->merge->allowFalse);
+ }
+
+ if (null !== $this->validation) {
+ $node->setFinalValidationClosures($this->validation->rules);
+ }
+
+ return $node;
+ }
+
+ /**
+ * Validate the configuration of a concrete node.
+ *
+ * @throws InvalidDefinitionException
+ */
+ protected function validateConcreteNode(ArrayNode $node)
+ {
+ $path = $node->getPath();
+
+ if (null !== $this->key) {
+ throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path));
+ }
+
+ if (false === $this->allowEmptyValue) {
+ throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path));
+ }
+
+ if (true === $this->atLeastOne) {
+ throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path));
+ }
+
+ if ($this->default) {
+ throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path));
+ }
+
+ if (false !== $this->addDefaultChildren) {
+ throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path));
+ }
+ }
+
+ /**
+ * Validate the configuration of a prototype node.
+ *
+ * @throws InvalidDefinitionException
+ */
+ protected function validatePrototypeNode(PrototypedArrayNode $node)
+ {
+ $path = $node->getPath();
+
+ if ($this->addDefaults) {
+ throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path));
+ }
+
+ if (false !== $this->addDefaultChildren) {
+ if ($this->default) {
+ throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s".', $path));
+ }
+
+ if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
+ throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path));
+ }
+
+ if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {
+ throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path));
+ }
+ }
+ }
+
+ /**
+ * @return NodeDefinition[]
+ */
+ public function getChildNodeDefinitions()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Finds a node defined by the given $nodePath.
+ *
+ * @param string $nodePath The path of the node to find. e.g "doctrine.orm.mappings"
+ */
+ public function find(string $nodePath): NodeDefinition
+ {
+ $firstPathSegment = (false === $pathSeparatorPos = strpos($nodePath, $this->pathSeparator))
+ ? $nodePath
+ : substr($nodePath, 0, $pathSeparatorPos);
+
+ if (null === $node = ($this->children[$firstPathSegment] ?? null)) {
+ throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name));
+ }
+
+ if (false === $pathSeparatorPos) {
+ return $node;
+ }
+
+ return $node->find(substr($nodePath, $pathSeparatorPos + \strlen($this->pathSeparator)));
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php
new file mode 100644
index 0000000..ace0b34
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.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\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\BooleanNode;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class BooleanNodeDefinition extends ScalarNodeDefinition
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(?string $name, NodeParentInterface $parent = null)
+ {
+ parent::__construct($name, $parent);
+
+ $this->nullEquivalent = true;
+ }
+
+ /**
+ * Instantiate a Node.
+ *
+ * @return BooleanNode
+ */
+ protected function instantiateNode()
+ {
+ return new BooleanNode($this->name, $this->parent, $this->pathSeparator);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidDefinitionException
+ */
+ public function cannotBeEmpty()
+ {
+ throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.');
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php b/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php
new file mode 100644
index 0000000..f30b873
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.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\Config\Definition\Builder;
+
+/**
+ * An interface that can be implemented by nodes which build other nodes.
+ *
+ * @author Roland Franssen
+ */
+interface BuilderAwareInterface
+{
+ /**
+ * Sets a custom children builder.
+ */
+ public function setBuilder(NodeBuilder $builder);
+}
diff --git a/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php
new file mode 100644
index 0000000..52e2fd1
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\EnumNode;
+
+/**
+ * Enum Node Definition.
+ *
+ * @author Johannes M. Schmitt
+ */
+class EnumNodeDefinition extends ScalarNodeDefinition
+{
+ private $values;
+
+ /**
+ * @return $this
+ */
+ public function values(array $values)
+ {
+ $values = array_unique($values);
+
+ if (empty($values)) {
+ throw new \InvalidArgumentException('->values() must be called with at least one value.');
+ }
+
+ $this->values = $values;
+
+ return $this;
+ }
+
+ /**
+ * Instantiate a Node.
+ *
+ * @return EnumNode
+ *
+ * @throws \RuntimeException
+ */
+ protected function instantiateNode()
+ {
+ if (null === $this->values) {
+ throw new \RuntimeException('You must call ->values() on enum nodes.');
+ }
+
+ return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/vendor/symfony/config/Definition/Builder/ExprBuilder.php
new file mode 100644
index 0000000..14387b5
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/ExprBuilder.php
@@ -0,0 +1,246 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+
+/**
+ * This class builds an if expression.
+ *
+ * @author Johannes M. Schmitt
+ * @author Christophe Coevoet
+ */
+class ExprBuilder
+{
+ protected $node;
+ public $ifPart;
+ public $thenPart;
+
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Marks the expression as being always used.
+ *
+ * @return $this
+ */
+ public function always(\Closure $then = null)
+ {
+ $this->ifPart = function () { return true; };
+
+ if (null !== $then) {
+ $this->thenPart = $then;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure to use as tests.
+ *
+ * The default one tests if the value is true.
+ *
+ * @return $this
+ */
+ public function ifTrue(\Closure $closure = null)
+ {
+ if (null === $closure) {
+ $closure = function ($v) { return true === $v; };
+ }
+
+ $this->ifPart = $closure;
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is a string.
+ *
+ * @return $this
+ */
+ public function ifString()
+ {
+ $this->ifPart = function ($v) { return \is_string($v); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is null.
+ *
+ * @return $this
+ */
+ public function ifNull()
+ {
+ $this->ifPart = function ($v) { return null === $v; };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is empty.
+ *
+ * @return $this
+ */
+ public function ifEmpty()
+ {
+ $this->ifPart = function ($v) { return empty($v); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is an array.
+ *
+ * @return $this
+ */
+ public function ifArray()
+ {
+ $this->ifPart = function ($v) { return \is_array($v); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is in an array.
+ *
+ * @return $this
+ */
+ public function ifInArray(array $array)
+ {
+ $this->ifPart = function ($v) use ($array) { return \in_array($v, $array, true); };
+
+ return $this;
+ }
+
+ /**
+ * Tests if the value is not in an array.
+ *
+ * @return $this
+ */
+ public function ifNotInArray(array $array)
+ {
+ $this->ifPart = function ($v) use ($array) { return !\in_array($v, $array, true); };
+
+ return $this;
+ }
+
+ /**
+ * Transforms variables of any type into an array.
+ *
+ * @return $this
+ */
+ public function castToArray()
+ {
+ $this->ifPart = function ($v) { return !\is_array($v); };
+ $this->thenPart = function ($v) { return [$v]; };
+
+ return $this;
+ }
+
+ /**
+ * Sets the closure to run if the test pass.
+ *
+ * @return $this
+ */
+ public function then(\Closure $closure)
+ {
+ $this->thenPart = $closure;
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure returning an empty array.
+ *
+ * @return $this
+ */
+ public function thenEmptyArray()
+ {
+ $this->thenPart = function () { return []; };
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure marking the value as invalid at processing time.
+ *
+ * if you want to add the value of the node in your message just use a %s placeholder.
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function thenInvalid(string $message)
+ {
+ $this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
+
+ return $this;
+ }
+
+ /**
+ * Sets a closure unsetting this key of the array at processing time.
+ *
+ * @return $this
+ *
+ * @throws UnsetKeyException
+ */
+ public function thenUnset()
+ {
+ $this->thenPart = function () { throw new UnsetKeyException('Unsetting key.'); };
+
+ return $this;
+ }
+
+ /**
+ * Returns the related node.
+ *
+ * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
+ *
+ * @throws \RuntimeException
+ */
+ public function end()
+ {
+ if (null === $this->ifPart) {
+ throw new \RuntimeException('You must specify an if part.');
+ }
+ if (null === $this->thenPart) {
+ throw new \RuntimeException('You must specify a then part.');
+ }
+
+ return $this->node;
+ }
+
+ /**
+ * Builds the expressions.
+ *
+ * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build
+ *
+ * @return array
+ */
+ public static function buildExpressions(array $expressions)
+ {
+ foreach ($expressions as $k => $expr) {
+ if ($expr instanceof self) {
+ $if = $expr->ifPart;
+ $then = $expr->thenPart;
+ $expressions[$k] = function ($v) use ($if, $then) {
+ return $if($v) ? $then($v) : $v;
+ };
+ }
+ }
+
+ return $expressions;
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php
new file mode 100644
index 0000000..f50f190
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\FloatNode;
+
+/**
+ * This class provides a fluent interface for defining a float node.
+ *
+ * @author Jeanmonod David
+ */
+class FloatNodeDefinition extends NumericNodeDefinition
+{
+ /**
+ * Instantiates a Node.
+ *
+ * @return FloatNode
+ */
+ protected function instantiateNode()
+ {
+ return new FloatNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php
new file mode 100644
index 0000000..d28e5ae
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\IntegerNode;
+
+/**
+ * This class provides a fluent interface for defining an integer node.
+ *
+ * @author Jeanmonod David
+ */
+class IntegerNodeDefinition extends NumericNodeDefinition
+{
+ /**
+ * Instantiates a Node.
+ *
+ * @return IntegerNode
+ */
+ protected function instantiateNode()
+ {
+ return new IntegerNode($this->name, $this->parent, $this->min, $this->max, $this->pathSeparator);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/MergeBuilder.php b/vendor/symfony/config/Definition/Builder/MergeBuilder.php
new file mode 100644
index 0000000..a88d49b
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/MergeBuilder.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class builds merge conditions.
+ *
+ * @author Johannes M. Schmitt
+ */
+class MergeBuilder
+{
+ protected $node;
+ public $allowFalse = false;
+ public $allowOverwrite = true;
+
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Sets whether the node can be unset.
+ *
+ * @return $this
+ */
+ public function allowUnset(bool $allow = true)
+ {
+ $this->allowFalse = $allow;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether the node can be overwritten.
+ *
+ * @return $this
+ */
+ public function denyOverwrite(bool $deny = true)
+ {
+ $this->allowOverwrite = !$deny;
+
+ return $this;
+ }
+
+ /**
+ * Returns the related node.
+ *
+ * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition
+ */
+ public function end()
+ {
+ return $this->node;
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/vendor/symfony/config/Definition/Builder/NodeBuilder.php
new file mode 100644
index 0000000..245e972
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/NodeBuilder.php
@@ -0,0 +1,219 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class provides a fluent interface for building a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class NodeBuilder implements NodeParentInterface
+{
+ protected $parent;
+ protected $nodeMapping;
+
+ public function __construct()
+ {
+ $this->nodeMapping = [
+ 'variable' => VariableNodeDefinition::class,
+ 'scalar' => ScalarNodeDefinition::class,
+ 'boolean' => BooleanNodeDefinition::class,
+ 'integer' => IntegerNodeDefinition::class,
+ 'float' => FloatNodeDefinition::class,
+ 'array' => ArrayNodeDefinition::class,
+ 'enum' => EnumNodeDefinition::class,
+ ];
+ }
+
+ /**
+ * Set the parent node.
+ *
+ * @return $this
+ */
+ public function setParent(ParentNodeDefinitionInterface $parent = null)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Creates a child array node.
+ *
+ * @return ArrayNodeDefinition
+ */
+ public function arrayNode(string $name)
+ {
+ return $this->node($name, 'array');
+ }
+
+ /**
+ * Creates a child scalar node.
+ *
+ * @return ScalarNodeDefinition
+ */
+ public function scalarNode(string $name)
+ {
+ return $this->node($name, 'scalar');
+ }
+
+ /**
+ * Creates a child Boolean node.
+ *
+ * @return BooleanNodeDefinition
+ */
+ public function booleanNode(string $name)
+ {
+ return $this->node($name, 'boolean');
+ }
+
+ /**
+ * Creates a child integer node.
+ *
+ * @return IntegerNodeDefinition
+ */
+ public function integerNode(string $name)
+ {
+ return $this->node($name, 'integer');
+ }
+
+ /**
+ * Creates a child float node.
+ *
+ * @return FloatNodeDefinition
+ */
+ public function floatNode(string $name)
+ {
+ return $this->node($name, 'float');
+ }
+
+ /**
+ * Creates a child EnumNode.
+ *
+ * @return EnumNodeDefinition
+ */
+ public function enumNode(string $name)
+ {
+ return $this->node($name, 'enum');
+ }
+
+ /**
+ * Creates a child variable node.
+ *
+ * @return VariableNodeDefinition
+ */
+ public function variableNode(string $name)
+ {
+ return $this->node($name, 'variable');
+ }
+
+ /**
+ * Returns the parent node.
+ *
+ * @return NodeDefinition&ParentNodeDefinitionInterface
+ */
+ public function end()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Creates a child node.
+ *
+ * @return NodeDefinition
+ *
+ * @throws \RuntimeException When the node type is not registered
+ * @throws \RuntimeException When the node class is not found
+ */
+ public function node(?string $name, string $type)
+ {
+ $class = $this->getNodeClass($type);
+
+ $node = new $class($name);
+
+ $this->append($node);
+
+ return $node;
+ }
+
+ /**
+ * Appends a node definition.
+ *
+ * Usage:
+ *
+ * $node = new ArrayNodeDefinition('name')
+ * ->children()
+ * ->scalarNode('foo')->end()
+ * ->scalarNode('baz')->end()
+ * ->append($this->getBarNodeDefinition())
+ * ->end()
+ * ;
+ *
+ * @return $this
+ */
+ public function append(NodeDefinition $node)
+ {
+ if ($node instanceof BuilderAwareInterface) {
+ $builder = clone $this;
+ $builder->setParent(null);
+ $node->setBuilder($builder);
+ }
+
+ if (null !== $this->parent) {
+ $this->parent->append($node);
+ // Make this builder the node parent to allow for a fluid interface
+ $node->setParent($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds or overrides a node Type.
+ *
+ * @param string $type The name of the type
+ * @param string $class The fully qualified name the node definition class
+ *
+ * @return $this
+ */
+ public function setNodeClass(string $type, string $class)
+ {
+ $this->nodeMapping[strtolower($type)] = $class;
+
+ return $this;
+ }
+
+ /**
+ * Returns the class name of the node definition.
+ *
+ * @return string
+ *
+ * @throws \RuntimeException When the node type is not registered
+ * @throws \RuntimeException When the node class is not found
+ */
+ protected function getNodeClass(string $type)
+ {
+ $type = strtolower($type);
+
+ if (!isset($this->nodeMapping[$type])) {
+ throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type));
+ }
+
+ $class = $this->nodeMapping[$type];
+
+ if (!class_exists($class)) {
+ throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class));
+ }
+
+ return $class;
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/vendor/symfony/config/Definition/Builder/NodeDefinition.php
new file mode 100644
index 0000000..cf153f0
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/NodeDefinition.php
@@ -0,0 +1,383 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\BaseNode;
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+use Symfony\Component\Config\Definition\NodeInterface;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+abstract class NodeDefinition implements NodeParentInterface
+{
+ protected $name;
+ protected $normalization;
+ protected $validation;
+ protected $defaultValue;
+ protected $default = false;
+ protected $required = false;
+ protected $deprecation = [];
+ protected $merge;
+ protected $allowEmptyValue = true;
+ protected $nullEquivalent;
+ protected $trueEquivalent = true;
+ protected $falseEquivalent = false;
+ protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR;
+ protected $parent;
+ protected $attributes = [];
+
+ public function __construct(?string $name, NodeParentInterface $parent = null)
+ {
+ $this->parent = $parent;
+ $this->name = $name;
+ }
+
+ /**
+ * Sets the parent node.
+ *
+ * @return $this
+ */
+ public function setParent(NodeParentInterface $parent)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Sets info message.
+ *
+ * @return $this
+ */
+ public function info(string $info)
+ {
+ return $this->attribute('info', $info);
+ }
+
+ /**
+ * Sets example configuration.
+ *
+ * @param string|array $example
+ *
+ * @return $this
+ */
+ public function example($example)
+ {
+ return $this->attribute('example', $example);
+ }
+
+ /**
+ * Sets an attribute on the node.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function attribute(string $key, $value)
+ {
+ $this->attributes[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Returns the parent node.
+ *
+ * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null
+ */
+ public function end()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Creates the node.
+ *
+ * @return NodeInterface
+ */
+ public function getNode(bool $forceRootNode = false)
+ {
+ if ($forceRootNode) {
+ $this->parent = null;
+ }
+
+ if (null !== $this->normalization) {
+ $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before);
+ }
+
+ if (null !== $this->validation) {
+ $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules);
+ }
+
+ $node = $this->createNode();
+ if ($node instanceof BaseNode) {
+ $node->setAttributes($this->attributes);
+ }
+
+ return $node;
+ }
+
+ /**
+ * Sets the default value.
+ *
+ * @param mixed $value The default value
+ *
+ * @return $this
+ */
+ public function defaultValue($value)
+ {
+ $this->default = true;
+ $this->defaultValue = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the node as required.
+ *
+ * @return $this
+ */
+ public function isRequired()
+ {
+ $this->required = true;
+
+ return $this;
+ }
+
+ /**
+ * Sets the node as deprecated.
+ *
+ * @param string $package The name of the composer package that is triggering the deprecation
+ * @param string $version The version of the package that introduced the deprecation
+ * @param string $message the deprecation message to use
+ *
+ * You can use %node% and %path% placeholders in your message to display,
+ * respectively, the node name and its complete path
+ *
+ * @return $this
+ */
+ public function setDeprecated(/* string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.' */)
+ {
+ $args = \func_get_args();
+
+ if (\func_num_args() < 2) {
+ trigger_deprecation('symfony/config', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
+
+ $message = $args[0] ?? 'The child node "%node%" at path "%path%" is deprecated.';
+ $package = $version = '';
+ } else {
+ $package = (string) $args[0];
+ $version = (string) $args[1];
+ $message = (string) ($args[2] ?? 'The child node "%node%" at path "%path%" is deprecated.');
+ }
+
+ $this->deprecation = [
+ 'package' => $package,
+ 'version' => $version,
+ 'message' => $message,
+ ];
+
+ return $this;
+ }
+
+ /**
+ * Sets the equivalent value used when the node contains null.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function treatNullLike($value)
+ {
+ $this->nullEquivalent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the equivalent value used when the node contains true.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function treatTrueLike($value)
+ {
+ $this->trueEquivalent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets the equivalent value used when the node contains false.
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function treatFalseLike($value)
+ {
+ $this->falseEquivalent = $value;
+
+ return $this;
+ }
+
+ /**
+ * Sets null as the default value.
+ *
+ * @return $this
+ */
+ public function defaultNull()
+ {
+ return $this->defaultValue(null);
+ }
+
+ /**
+ * Sets true as the default value.
+ *
+ * @return $this
+ */
+ public function defaultTrue()
+ {
+ return $this->defaultValue(true);
+ }
+
+ /**
+ * Sets false as the default value.
+ *
+ * @return $this
+ */
+ public function defaultFalse()
+ {
+ return $this->defaultValue(false);
+ }
+
+ /**
+ * Sets an expression to run before the normalization.
+ *
+ * @return ExprBuilder
+ */
+ public function beforeNormalization()
+ {
+ return $this->normalization()->before();
+ }
+
+ /**
+ * Denies the node value being empty.
+ *
+ * @return $this
+ */
+ public function cannotBeEmpty()
+ {
+ $this->allowEmptyValue = false;
+
+ return $this;
+ }
+
+ /**
+ * Sets an expression to run for the validation.
+ *
+ * The expression receives the value of the node and must return it. It can
+ * modify it.
+ * An exception should be thrown when the node is not valid.
+ *
+ * @return ExprBuilder
+ */
+ public function validate()
+ {
+ return $this->validation()->rule();
+ }
+
+ /**
+ * Sets whether the node can be overwritten.
+ *
+ * @return $this
+ */
+ public function cannotBeOverwritten(bool $deny = true)
+ {
+ $this->merge()->denyOverwrite($deny);
+
+ return $this;
+ }
+
+ /**
+ * Gets the builder for validation rules.
+ *
+ * @return ValidationBuilder
+ */
+ protected function validation()
+ {
+ if (null === $this->validation) {
+ $this->validation = new ValidationBuilder($this);
+ }
+
+ return $this->validation;
+ }
+
+ /**
+ * Gets the builder for merging rules.
+ *
+ * @return MergeBuilder
+ */
+ protected function merge()
+ {
+ if (null === $this->merge) {
+ $this->merge = new MergeBuilder($this);
+ }
+
+ return $this->merge;
+ }
+
+ /**
+ * Gets the builder for normalization rules.
+ *
+ * @return NormalizationBuilder
+ */
+ protected function normalization()
+ {
+ if (null === $this->normalization) {
+ $this->normalization = new NormalizationBuilder($this);
+ }
+
+ return $this->normalization;
+ }
+
+ /**
+ * Instantiate and configure the node according to this definition.
+ *
+ * @return NodeInterface
+ *
+ * @throws InvalidDefinitionException When the definition is invalid
+ */
+ abstract protected function createNode();
+
+ /**
+ * Set PathSeparator to use.
+ *
+ * @return $this
+ */
+ public function setPathSeparator(string $separator)
+ {
+ if ($this instanceof ParentNodeDefinitionInterface) {
+ foreach ($this->getChildNodeDefinitions() as $child) {
+ $child->setPathSeparator($separator);
+ }
+ }
+
+ $this->pathSeparator = $separator;
+
+ return $this;
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/NodeParentInterface.php b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php
new file mode 100644
index 0000000..305e993
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * An interface that must be implemented by all node parents.
+ *
+ * @author Victor Berchet
+ */
+interface NodeParentInterface
+{
+}
diff --git a/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php
new file mode 100644
index 0000000..06cbbd4
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * This class builds normalization conditions.
+ *
+ * @author Johannes M. Schmitt
+ */
+class NormalizationBuilder
+{
+ protected $node;
+ public $before = [];
+ public $remappings = [];
+
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Registers a key to remap to its plural form.
+ *
+ * @param string $key The key to remap
+ * @param string|null $plural The plural of the key in case of irregular plural
+ *
+ * @return $this
+ */
+ public function remap(string $key, string $plural = null)
+ {
+ $this->remappings[] = [$key, null === $plural ? $key.'s' : $plural];
+
+ return $this;
+ }
+
+ /**
+ * Registers a closure to run before the normalization or an expression builder to build it if null is provided.
+ *
+ * @return ExprBuilder|$this
+ */
+ public function before(\Closure $closure = null)
+ {
+ if (null !== $closure) {
+ $this->before[] = $closure;
+
+ return $this;
+ }
+
+ return $this->before[] = new ExprBuilder($this->node);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php
new file mode 100644
index 0000000..c4bff17
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException;
+
+/**
+ * Abstract class that contains common code of integer and float node definitions.
+ *
+ * @author David Jeanmonod
+ */
+abstract class NumericNodeDefinition extends ScalarNodeDefinition
+{
+ protected $min;
+ protected $max;
+
+ /**
+ * Ensures that the value is smaller than the given reference.
+ *
+ * @param int|float $max
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException when the constraint is inconsistent
+ */
+ public function max($max)
+ {
+ if (isset($this->min) && $this->min > $max) {
+ throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min));
+ }
+ $this->max = $max;
+
+ return $this;
+ }
+
+ /**
+ * Ensures that the value is bigger than the given reference.
+ *
+ * @param int|float $min
+ *
+ * @return $this
+ *
+ * @throws \InvalidArgumentException when the constraint is inconsistent
+ */
+ public function min($min)
+ {
+ if (isset($this->max) && $this->max < $min) {
+ throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max));
+ }
+ $this->min = $min;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws InvalidDefinitionException
+ */
+ public function cannotBeEmpty()
+ {
+ throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to NumericNodeDefinition.');
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php
new file mode 100644
index 0000000..449b91a
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+/**
+ * An interface that must be implemented by nodes which can have children.
+ *
+ * @author Victor Berchet
+ */
+interface ParentNodeDefinitionInterface extends BuilderAwareInterface
+{
+ /**
+ * Returns a builder to add children nodes.
+ *
+ * @return NodeBuilder
+ */
+ public function children();
+
+ /**
+ * Appends a node definition.
+ *
+ * Usage:
+ *
+ * $node = $parentNode
+ * ->children()
+ * ->scalarNode('foo')->end()
+ * ->scalarNode('baz')->end()
+ * ->append($this->getBarNodeDefinition())
+ * ->end()
+ * ;
+ *
+ * @return $this
+ */
+ public function append(NodeDefinition $node);
+
+ /**
+ * Gets the child node definitions.
+ *
+ * @return NodeDefinition[]
+ */
+ public function getChildNodeDefinitions();
+}
diff --git a/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php
new file mode 100644
index 0000000..076f74b
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\ScalarNode;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ScalarNodeDefinition extends VariableNodeDefinition
+{
+ /**
+ * Instantiate a Node.
+ *
+ * @return ScalarNode
+ */
+ protected function instantiateNode()
+ {
+ return new ScalarNode($this->name, $this->parent, $this->pathSeparator);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/vendor/symfony/config/Definition/Builder/TreeBuilder.php
new file mode 100644
index 0000000..f3c3c21
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/TreeBuilder.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\NodeInterface;
+
+/**
+ * This is the entry class for building a config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class TreeBuilder implements NodeParentInterface
+{
+ protected $tree;
+ protected $root;
+
+ public function __construct(string $name, string $type = 'array', NodeBuilder $builder = null)
+ {
+ $builder = $builder ?? new NodeBuilder();
+ $this->root = $builder->node($name, $type)->setParent($this);
+ }
+
+ /**
+ * @return NodeDefinition|ArrayNodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array')
+ */
+ public function getRootNode(): NodeDefinition
+ {
+ return $this->root;
+ }
+
+ /**
+ * Builds the tree.
+ *
+ * @return NodeInterface
+ *
+ * @throws \RuntimeException
+ */
+ public function buildTree()
+ {
+ if (null !== $this->tree) {
+ return $this->tree;
+ }
+
+ return $this->tree = $this->root->getNode(true);
+ }
+
+ public function setPathSeparator(string $separator)
+ {
+ // unset last built as changing path separator changes all nodes
+ $this->tree = null;
+
+ $this->root->setPathSeparator($separator);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/ValidationBuilder.php b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php
new file mode 100644
index 0000000..4efc726
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/ValidationBuilder.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\Config\Definition\Builder;
+
+/**
+ * This class builds validation conditions.
+ *
+ * @author Christophe Coevoet
+ */
+class ValidationBuilder
+{
+ protected $node;
+ public $rules = [];
+
+ public function __construct(NodeDefinition $node)
+ {
+ $this->node = $node;
+ }
+
+ /**
+ * Registers a closure to run as normalization or an expression builder to build it if null is provided.
+ *
+ * @return ExprBuilder|$this
+ */
+ public function rule(\Closure $closure = null)
+ {
+ if (null !== $closure) {
+ $this->rules[] = $closure;
+
+ return $this;
+ }
+
+ return $this->rules[] = new ExprBuilder($this->node);
+ }
+}
diff --git a/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php
new file mode 100644
index 0000000..eea16cc
--- /dev/null
+++ b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Builder;
+
+use Symfony\Component\Config\Definition\VariableNode;
+
+/**
+ * This class provides a fluent interface for defining a node.
+ *
+ * @author Johannes M. Schmitt
+ */
+class VariableNodeDefinition extends NodeDefinition
+{
+ /**
+ * Instantiate a Node.
+ *
+ * @return VariableNode
+ */
+ protected function instantiateNode()
+ {
+ return new VariableNode($this->name, $this->parent, $this->pathSeparator);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function createNode()
+ {
+ $node = $this->instantiateNode();
+
+ if (null !== $this->normalization) {
+ $node->setNormalizationClosures($this->normalization->before);
+ }
+
+ if (null !== $this->merge) {
+ $node->setAllowOverwrite($this->merge->allowOverwrite);
+ }
+
+ if (true === $this->default) {
+ $node->setDefaultValue($this->defaultValue);
+ }
+
+ $node->setAllowEmptyValue($this->allowEmptyValue);
+ $node->addEquivalentValue(null, $this->nullEquivalent);
+ $node->addEquivalentValue(true, $this->trueEquivalent);
+ $node->addEquivalentValue(false, $this->falseEquivalent);
+ $node->setRequired($this->required);
+
+ if ($this->deprecation) {
+ $node->setDeprecated($this->deprecation['package'], $this->deprecation['version'], $this->deprecation['message']);
+ }
+
+ if (null !== $this->validation) {
+ $node->setFinalValidationClosures($this->validation->rules);
+ }
+
+ return $node;
+ }
+}
diff --git a/vendor/symfony/config/Definition/ConfigurationInterface.php b/vendor/symfony/config/Definition/ConfigurationInterface.php
new file mode 100644
index 0000000..7b5d443
--- /dev/null
+++ b/vendor/symfony/config/Definition/ConfigurationInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+
+/**
+ * Configuration interface.
+ *
+ * @author Victor Berchet
+ */
+interface ConfigurationInterface
+{
+ /**
+ * Generates the configuration tree builder.
+ *
+ * @return TreeBuilder
+ */
+ public function getConfigTreeBuilder();
+}
diff --git a/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php
new file mode 100644
index 0000000..a8b18a0
--- /dev/null
+++ b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php
@@ -0,0 +1,306 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Dumper;
+
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\BaseNode;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\EnumNode;
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+
+/**
+ * Dumps an XML reference configuration for the given configuration/node instance.
+ *
+ * @author Wouter J
+ */
+class XmlReferenceDumper
+{
+ private $reference;
+
+ public function dump(ConfigurationInterface $configuration, string $namespace = null)
+ {
+ return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace);
+ }
+
+ public function dumpNode(NodeInterface $node, string $namespace = null)
+ {
+ $this->reference = '';
+ $this->writeNode($node, 0, true, $namespace);
+ $ref = $this->reference;
+ $this->reference = null;
+
+ return $ref;
+ }
+
+ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = false, string $namespace = null)
+ {
+ $rootName = ($root ? 'config' : $node->getName());
+ $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null));
+
+ // xml remapping
+ if ($node->getParent()) {
+ $remapping = array_filter($node->getParent()->getXmlRemappings(), function (array $mapping) use ($rootName) {
+ return $rootName === $mapping[1];
+ });
+
+ if (\count($remapping)) {
+ [$singular] = current($remapping);
+ $rootName = $singular;
+ }
+ }
+ $rootName = str_replace('_', '-', $rootName);
+
+ $rootAttributes = [];
+ $rootAttributeComments = [];
+ $rootChildren = [];
+ $rootComments = [];
+
+ if ($node instanceof ArrayNode) {
+ $children = $node->getChildren();
+
+ // comments about the root node
+ if ($rootInfo = $node->getInfo()) {
+ $rootComments[] = $rootInfo;
+ }
+
+ if ($rootNamespace) {
+ $rootComments[] = 'Namespace: '.$rootNamespace;
+ }
+
+ // render prototyped nodes
+ if ($node instanceof PrototypedArrayNode) {
+ $prototype = $node->getPrototype();
+
+ $info = 'prototype';
+ if (null !== $prototype->getInfo()) {
+ $info .= ': '.$prototype->getInfo();
+ }
+ array_unshift($rootComments, $info);
+
+ if ($key = $node->getKeyAttribute()) {
+ $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key;
+ }
+
+ if ($prototype instanceof PrototypedArrayNode) {
+ $prototype->setName($key ?? '');
+ $children = [$key => $prototype];
+ } elseif ($prototype instanceof ArrayNode) {
+ $children = $prototype->getChildren();
+ } else {
+ if ($prototype->hasDefaultValue()) {
+ $prototypeValue = $prototype->getDefaultValue();
+ } else {
+ switch (\get_class($prototype)) {
+ case 'Symfony\Component\Config\Definition\ScalarNode':
+ $prototypeValue = 'scalar value';
+ break;
+
+ case 'Symfony\Component\Config\Definition\FloatNode':
+ case 'Symfony\Component\Config\Definition\IntegerNode':
+ $prototypeValue = 'numeric value';
+ break;
+
+ case 'Symfony\Component\Config\Definition\BooleanNode':
+ $prototypeValue = 'true|false';
+ break;
+
+ case 'Symfony\Component\Config\Definition\EnumNode':
+ $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues()));
+ break;
+
+ default:
+ $prototypeValue = 'value';
+ }
+ }
+ }
+ }
+
+ // get attributes and elements
+ foreach ($children as $child) {
+ if ($child instanceof ArrayNode) {
+ // get elements
+ $rootChildren[] = $child;
+
+ continue;
+ }
+
+ // get attributes
+
+ // metadata
+ $name = str_replace('_', '-', $child->getName());
+ $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world
+
+ // comments
+ $comments = [];
+ if ($child instanceof BaseNode && $info = $child->getInfo()) {
+ $comments[] = $info;
+ }
+
+ if ($child instanceof BaseNode && $example = $child->getExample()) {
+ $comments[] = 'Example: '.$example;
+ }
+
+ if ($child->isRequired()) {
+ $comments[] = 'Required';
+ }
+
+ if ($child instanceof BaseNode && $child->isDeprecated()) {
+ $deprecation = $child->getDeprecation($child->getName(), $node->getPath());
+ $comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']);
+ }
+
+ if ($child instanceof EnumNode) {
+ $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues()));
+ }
+
+ if (\count($comments)) {
+ $rootAttributeComments[$name] = implode(";\n", $comments);
+ }
+
+ // default values
+ if ($child->hasDefaultValue()) {
+ $value = $child->getDefaultValue();
+ }
+
+ // append attribute
+ $rootAttributes[$name] = $value;
+ }
+ }
+
+ // render comments
+
+ // root node comment
+ if (\count($rootComments)) {
+ foreach ($rootComments as $comment) {
+ $this->writeLine('', $depth);
+ }
+ }
+
+ // attribute comments
+ if (\count($rootAttributeComments)) {
+ foreach ($rootAttributeComments as $attrName => $comment) {
+ $commentDepth = $depth + 4 + \strlen($attrName) + 2;
+ $commentLines = explode("\n", $comment);
+ $multiline = (\count($commentLines) > 1);
+ $comment = implode(\PHP_EOL.str_repeat(' ', $commentDepth), $commentLines);
+
+ if ($multiline) {
+ $this->writeLine('', $depth);
+ } else {
+ $this->writeLine('', $depth);
+ }
+ }
+ }
+
+ // render start tag + attributes
+ $rootIsVariablePrototype = isset($prototypeValue);
+ $rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype);
+ $rootOpenTag = '<'.$rootName;
+ if (1 >= ($attributesCount = \count($rootAttributes))) {
+ if (1 === $attributesCount) {
+ $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes)));
+ }
+
+ $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>';
+
+ if ($rootIsVariablePrototype) {
+ $rootOpenTag .= $prototypeValue.''.$rootName.'>';
+ }
+
+ $this->writeLine($rootOpenTag, $depth);
+ } else {
+ $this->writeLine($rootOpenTag, $depth);
+
+ $i = 1;
+
+ foreach ($rootAttributes as $attrName => $attrValue) {
+ $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue));
+
+ $this->writeLine($attr, $depth + 4);
+
+ if ($attributesCount === $i++) {
+ $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth);
+
+ if ($rootIsVariablePrototype) {
+ $rootOpenTag .= $prototypeValue.''.$rootName.'>';
+ }
+ }
+ }
+ }
+
+ // render children tags
+ foreach ($rootChildren as $child) {
+ $this->writeLine('');
+ $this->writeNode($child, $depth + 4);
+ }
+
+ // render end tag
+ if (!$rootIsEmptyTag && !$rootIsVariablePrototype) {
+ $this->writeLine('');
+
+ $rootEndTag = ''.$rootName.'>';
+ $this->writeLine($rootEndTag, $depth);
+ }
+ }
+
+ /**
+ * Outputs a single config reference line.
+ */
+ private function writeLine(string $text, int $indent = 0)
+ {
+ $indent = \strlen($text) + $indent;
+ $format = '%'.$indent.'s';
+
+ $this->reference .= sprintf($format, $text).\PHP_EOL;
+ }
+
+ /**
+ * Renders the string conversion of the value.
+ *
+ * @param mixed $value
+ */
+ private function writeValue($value): string
+ {
+ if ('%%%%not_defined%%%%' === $value) {
+ return '';
+ }
+
+ if (\is_string($value) || is_numeric($value)) {
+ return $value;
+ }
+
+ if (false === $value) {
+ return 'false';
+ }
+
+ if (true === $value) {
+ return 'true';
+ }
+
+ if (null === $value) {
+ return 'null';
+ }
+
+ if (empty($value)) {
+ return '';
+ }
+
+ if (\is_array($value)) {
+ return implode(',', $value);
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php
new file mode 100644
index 0000000..6fcfb71
--- /dev/null
+++ b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php
@@ -0,0 +1,251 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Dumper;
+
+use Symfony\Component\Config\Definition\ArrayNode;
+use Symfony\Component\Config\Definition\BaseNode;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+use Symfony\Component\Config\Definition\EnumNode;
+use Symfony\Component\Config\Definition\NodeInterface;
+use Symfony\Component\Config\Definition\PrototypedArrayNode;
+use Symfony\Component\Config\Definition\ScalarNode;
+use Symfony\Component\Config\Definition\VariableNode;
+use Symfony\Component\Yaml\Inline;
+
+/**
+ * Dumps a Yaml reference configuration for the given configuration/node instance.
+ *
+ * @author Kevin Bond
+ */
+class YamlReferenceDumper
+{
+ private $reference;
+
+ public function dump(ConfigurationInterface $configuration)
+ {
+ return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree());
+ }
+
+ public function dumpAtPath(ConfigurationInterface $configuration, string $path)
+ {
+ $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree();
+
+ foreach (explode('.', $path) as $step) {
+ if (!$node instanceof ArrayNode) {
+ throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path));
+ }
+
+ /** @var NodeInterface[] $children */
+ $children = $node instanceof PrototypedArrayNode ? $this->getPrototypeChildren($node) : $node->getChildren();
+
+ foreach ($children as $child) {
+ if ($child->getName() === $step) {
+ $node = $child;
+
+ continue 2;
+ }
+ }
+
+ throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path));
+ }
+
+ return $this->dumpNode($node);
+ }
+
+ public function dumpNode(NodeInterface $node)
+ {
+ $this->reference = '';
+ $this->writeNode($node);
+ $ref = $this->reference;
+ $this->reference = null;
+
+ return $ref;
+ }
+
+ private function writeNode(NodeInterface $node, NodeInterface $parentNode = null, int $depth = 0, bool $prototypedArray = false)
+ {
+ $comments = [];
+ $default = '';
+ $defaultArray = null;
+ $children = null;
+ $example = null;
+ if ($node instanceof BaseNode) {
+ $example = $node->getExample();
+ }
+
+ // defaults
+ if ($node instanceof ArrayNode) {
+ $children = $node->getChildren();
+
+ if ($node instanceof PrototypedArrayNode) {
+ $children = $this->getPrototypeChildren($node);
+ }
+
+ if (!$children) {
+ if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) {
+ $default = '';
+ } elseif (!\is_array($example)) {
+ $default = '[]';
+ }
+ }
+ } elseif ($node instanceof EnumNode) {
+ $comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues()));
+ $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~';
+ } elseif (VariableNode::class === \get_class($node) && \is_array($example)) {
+ // If there is an array example, we are sure we dont need to print a default value
+ $default = '';
+ } else {
+ $default = '~';
+
+ if ($node->hasDefaultValue()) {
+ $default = $node->getDefaultValue();
+
+ if (\is_array($default)) {
+ if (\count($defaultArray = $node->getDefaultValue())) {
+ $default = '';
+ } elseif (!\is_array($example)) {
+ $default = '[]';
+ }
+ } else {
+ $default = Inline::dump($default);
+ }
+ }
+ }
+
+ // required?
+ if ($node->isRequired()) {
+ $comments[] = 'Required';
+ }
+
+ // deprecated?
+ if ($node instanceof BaseNode && $node->isDeprecated()) {
+ $deprecation = $node->getDeprecation($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath());
+ $comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']);
+ }
+
+ // example
+ if ($example && !\is_array($example)) {
+ $comments[] = 'Example: '.Inline::dump($example);
+ }
+
+ $default = '' != (string) $default ? ' '.$default : '';
+ $comments = \count($comments) ? '# '.implode(', ', $comments) : '';
+
+ $key = $prototypedArray ? '-' : $node->getName().':';
+ $text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' ');
+
+ if ($node instanceof BaseNode && $info = $node->getInfo()) {
+ $this->writeLine('');
+ // indenting multi-line info
+ $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info);
+ $this->writeLine('# '.$info, $depth * 4);
+ }
+
+ $this->writeLine($text, $depth * 4);
+
+ // output defaults
+ if ($defaultArray) {
+ $this->writeLine('');
+
+ $message = \count($defaultArray) > 1 ? 'Defaults' : 'Default';
+
+ $this->writeLine('# '.$message.':', $depth * 4 + 4);
+
+ $this->writeArray($defaultArray, $depth + 1);
+ }
+
+ if (\is_array($example)) {
+ $this->writeLine('');
+
+ $message = \count($example) > 1 ? 'Examples' : 'Example';
+
+ $this->writeLine('# '.$message.':', $depth * 4 + 4);
+
+ $this->writeArray(array_map([Inline::class, 'dump'], $example), $depth + 1);
+ }
+
+ if ($children) {
+ foreach ($children as $childNode) {
+ $this->writeNode($childNode, $node, $depth + 1, $node instanceof PrototypedArrayNode && !$node->getKeyAttribute());
+ }
+ }
+ }
+
+ /**
+ * Outputs a single config reference line.
+ */
+ private function writeLine(string $text, int $indent = 0)
+ {
+ $indent = \strlen($text) + $indent;
+ $format = '%'.$indent.'s';
+
+ $this->reference .= sprintf($format, $text)."\n";
+ }
+
+ private function writeArray(array $array, int $depth)
+ {
+ $isIndexed = array_values($array) === $array;
+
+ foreach ($array as $key => $value) {
+ if (\is_array($value)) {
+ $val = '';
+ } else {
+ $val = $value;
+ }
+
+ if ($isIndexed) {
+ $this->writeLine('- '.$val, $depth * 4);
+ } else {
+ $this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
+ }
+
+ if (\is_array($value)) {
+ $this->writeArray($value, $depth + 1);
+ }
+ }
+ }
+
+ private function getPrototypeChildren(PrototypedArrayNode $node): array
+ {
+ $prototype = $node->getPrototype();
+ $key = $node->getKeyAttribute();
+
+ // Do not expand prototype if it isn't an array node nor uses attribute as key
+ if (!$key && !$prototype instanceof ArrayNode) {
+ return $node->getChildren();
+ }
+
+ if ($prototype instanceof ArrayNode) {
+ $keyNode = new ArrayNode($key, $node);
+ $children = $prototype->getChildren();
+
+ if ($prototype instanceof PrototypedArrayNode && $prototype->getKeyAttribute()) {
+ $children = $this->getPrototypeChildren($prototype);
+ }
+
+ // add children
+ foreach ($children as $childNode) {
+ $keyNode->addChild($childNode);
+ }
+ } else {
+ $keyNode = new ScalarNode($key, $node);
+ }
+
+ $info = 'Prototype';
+ if (null !== $prototype->getInfo()) {
+ $info .= ': '.$prototype->getInfo();
+ }
+ $keyNode->setInfo($info);
+
+ return [$key => $keyNode];
+ }
+}
diff --git a/vendor/symfony/config/Definition/EnumNode.php b/vendor/symfony/config/Definition/EnumNode.php
new file mode 100644
index 0000000..822e6b5
--- /dev/null
+++ b/vendor/symfony/config/Definition/EnumNode.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\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+/**
+ * Node which only allows a finite set of values.
+ *
+ * @author Johannes M. Schmitt
+ */
+class EnumNode extends ScalarNode
+{
+ private $values;
+
+ public function __construct(?string $name, NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
+ {
+ $values = array_unique($values);
+ if (empty($values)) {
+ throw new \InvalidArgumentException('$values must contain at least one element.');
+ }
+
+ parent::__construct($name, $parent, $pathSeparator);
+ $this->values = $values;
+ }
+
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function finalizeValue($value)
+ {
+ $value = parent::finalizeValue($value);
+
+ if (!\in_array($value, $this->values, true)) {
+ $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values))));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function allowPlaceholders(): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php
new file mode 100644
index 0000000..48dd932
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is thrown whenever the key of an array is not unique. This can
+ * only be the case if the configuration is coming from an XML file.
+ *
+ * @author Johannes M. Schmitt
+ */
+class DuplicateKeyException extends InvalidConfigurationException
+{
+}
diff --git a/vendor/symfony/config/Definition/Exception/Exception.php b/vendor/symfony/config/Definition/Exception/Exception.php
new file mode 100644
index 0000000..8933a49
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/Exception.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * Base exception for all configuration exceptions.
+ *
+ * @author Johannes M. Schmitt
+ */
+class Exception extends \RuntimeException
+{
+}
diff --git a/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php
new file mode 100644
index 0000000..726c07f
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is thrown when a configuration path is overwritten from a
+ * subsequent configuration file, but the entry node specifically forbids this.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ForbiddenOverwriteException extends InvalidConfigurationException
+{
+}
diff --git a/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php
new file mode 100644
index 0000000..ceb5e23
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * A very general exception which can be thrown whenever non of the more specific
+ * exceptions is suitable.
+ *
+ * @author Johannes M. Schmitt
+ */
+class InvalidConfigurationException extends Exception
+{
+ private $path;
+ private $containsHints = false;
+
+ public function setPath(string $path)
+ {
+ $this->path = $path;
+ }
+
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Adds extra information that is suffixed to the original exception message.
+ */
+ public function addHint(string $hint)
+ {
+ if (!$this->containsHints) {
+ $this->message .= "\nHint: ".$hint;
+ $this->containsHints = true;
+ } else {
+ $this->message .= ', '.$hint;
+ }
+ }
+}
diff --git a/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php
new file mode 100644
index 0000000..98310da
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * Thrown when an error is detected in a node Definition.
+ *
+ * @author Victor Berchet
+ */
+class InvalidDefinitionException extends Exception
+{
+}
diff --git a/vendor/symfony/config/Definition/Exception/InvalidTypeException.php b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php
new file mode 100644
index 0000000..d7ca8c9
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is thrown if an invalid type is encountered.
+ *
+ * @author Johannes M. Schmitt
+ */
+class InvalidTypeException extends InvalidConfigurationException
+{
+}
diff --git a/vendor/symfony/config/Definition/Exception/UnsetKeyException.php b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php
new file mode 100644
index 0000000..863181a
--- /dev/null
+++ b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition\Exception;
+
+/**
+ * This exception is usually not encountered by the end-user, but only used
+ * internally to signal the parent scope to unset a key.
+ *
+ * @author Johannes M. Schmitt
+ */
+class UnsetKeyException extends Exception
+{
+}
diff --git a/vendor/symfony/config/Definition/FloatNode.php b/vendor/symfony/config/Definition/FloatNode.php
new file mode 100644
index 0000000..527f996
--- /dev/null
+++ b/vendor/symfony/config/Definition/FloatNode.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents a float value in the config tree.
+ *
+ * @author Jeanmonod David
+ */
+class FloatNode extends NumericNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ // Integers are also accepted, we just cast them
+ if (\is_int($value)) {
+ $value = (float) $value;
+ }
+
+ if (!\is_float($value)) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "float", but got "%s".', $this->getPath(), get_debug_type($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getValidPlaceholderTypes(): array
+ {
+ return ['float'];
+ }
+}
diff --git a/vendor/symfony/config/Definition/IntegerNode.php b/vendor/symfony/config/Definition/IntegerNode.php
new file mode 100644
index 0000000..dfb4cc6
--- /dev/null
+++ b/vendor/symfony/config/Definition/IntegerNode.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents an integer value in the config tree.
+ *
+ * @author Jeanmonod David
+ */
+class IntegerNode extends NumericNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!\is_int($value)) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getValidPlaceholderTypes(): array
+ {
+ return ['int'];
+ }
+}
diff --git a/vendor/symfony/config/Definition/NodeInterface.php b/vendor/symfony/config/Definition/NodeInterface.php
new file mode 100644
index 0000000..9c279ae
--- /dev/null
+++ b/vendor/symfony/config/Definition/NodeInterface.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * Common Interface among all nodes.
+ *
+ * In most cases, it is better to inherit from BaseNode instead of implementing
+ * this interface yourself.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface NodeInterface
+{
+ /**
+ * Returns the name of the node.
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Returns the path of the node.
+ *
+ * @return string
+ */
+ public function getPath();
+
+ /**
+ * Returns true when the node is required.
+ *
+ * @return bool
+ */
+ public function isRequired();
+
+ /**
+ * Returns true when the node has a default value.
+ *
+ * @return bool
+ */
+ public function hasDefaultValue();
+
+ /**
+ * Returns the default value of the node.
+ *
+ * @return mixed
+ *
+ * @throws \RuntimeException if the node has no default value
+ */
+ public function getDefaultValue();
+
+ /**
+ * Normalizes a value.
+ *
+ * @param mixed $value The value to normalize
+ *
+ * @return mixed
+ *
+ * @throws InvalidTypeException if the value type is invalid
+ */
+ public function normalize($value);
+
+ /**
+ * Merges two values together.
+ *
+ * @param mixed $leftSide
+ * @param mixed $rightSide
+ *
+ * @return mixed
+ *
+ * @throws ForbiddenOverwriteException if the configuration path cannot be overwritten
+ * @throws InvalidTypeException if the value type is invalid
+ */
+ public function merge($leftSide, $rightSide);
+
+ /**
+ * Finalizes a value.
+ *
+ * @param mixed $value The value to finalize
+ *
+ * @return mixed
+ *
+ * @throws InvalidTypeException if the value type is invalid
+ * @throws InvalidConfigurationException if the value is invalid configuration
+ */
+ public function finalize($value);
+}
diff --git a/vendor/symfony/config/Definition/NumericNode.php b/vendor/symfony/config/Definition/NumericNode.php
new file mode 100644
index 0000000..50d137c
--- /dev/null
+++ b/vendor/symfony/config/Definition/NumericNode.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+/**
+ * This node represents a numeric value in the config tree.
+ *
+ * @author David Jeanmonod
+ */
+class NumericNode extends ScalarNode
+{
+ protected $min;
+ protected $max;
+
+ /**
+ * @param int|float|null $min
+ * @param int|float|null $max
+ */
+ public function __construct(?string $name, NodeInterface $parent = null, $min = null, $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR)
+ {
+ parent::__construct($name, $parent, $pathSeparator);
+ $this->min = $min;
+ $this->max = $max;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function finalizeValue($value)
+ {
+ $value = parent::finalizeValue($value);
+
+ $errorMsg = null;
+ if (isset($this->min) && $value < $this->min) {
+ $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min);
+ }
+ if (isset($this->max) && $value > $this->max) {
+ $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max);
+ }
+ if (isset($errorMsg)) {
+ $ex = new InvalidConfigurationException($errorMsg);
+ $ex->setPath($this->getPath());
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isValueEmpty($value)
+ {
+ // a numeric value cannot be empty
+ return false;
+ }
+}
diff --git a/vendor/symfony/config/Definition/Processor.php b/vendor/symfony/config/Definition/Processor.php
new file mode 100644
index 0000000..c431408
--- /dev/null
+++ b/vendor/symfony/config/Definition/Processor.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+/**
+ * This class is the entry point for config normalization/merging/finalization.
+ *
+ * @author Johannes M. Schmitt
+ *
+ * @final
+ */
+class Processor
+{
+ /**
+ * Processes an array of configurations.
+ *
+ * @param array $configs An array of configuration items to process
+ */
+ public function process(NodeInterface $configTree, array $configs): array
+ {
+ $currentConfig = [];
+ foreach ($configs as $config) {
+ $config = $configTree->normalize($config);
+ $currentConfig = $configTree->merge($currentConfig, $config);
+ }
+
+ return $configTree->finalize($currentConfig);
+ }
+
+ /**
+ * Processes an array of configurations.
+ *
+ * @param array $configs An array of configuration items to process
+ */
+ public function processConfiguration(ConfigurationInterface $configuration, array $configs): array
+ {
+ return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs);
+ }
+
+ /**
+ * Normalizes a configuration entry.
+ *
+ * This method returns a normalize configuration array for a given key
+ * to remove the differences due to the original format (YAML and XML mainly).
+ *
+ * Here is an example.
+ *
+ * The configuration in XML:
+ *
+ * twig.extension.foo
+ * twig.extension.bar
+ *
+ * And the same configuration in YAML:
+ *
+ * extensions: ['twig.extension.foo', 'twig.extension.bar']
+ *
+ * @param array $config A config array
+ * @param string $key The key to normalize
+ * @param string $plural The plural form of the key if it is irregular
+ */
+ public static function normalizeConfig(array $config, string $key, string $plural = null): array
+ {
+ if (null === $plural) {
+ $plural = $key.'s';
+ }
+
+ if (isset($config[$plural])) {
+ return $config[$plural];
+ }
+
+ if (isset($config[$key])) {
+ if (\is_string($config[$key]) || !\is_int(key($config[$key]))) {
+ // only one
+ return [$config[$key]];
+ }
+
+ return $config[$key];
+ }
+
+ return [];
+ }
+}
diff --git a/vendor/symfony/config/Definition/PrototypeNodeInterface.php b/vendor/symfony/config/Definition/PrototypeNodeInterface.php
new file mode 100644
index 0000000..b160aa9
--- /dev/null
+++ b/vendor/symfony/config/Definition/PrototypeNodeInterface.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\Config\Definition;
+
+/**
+ * This interface must be implemented by nodes which can be used as prototypes.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface PrototypeNodeInterface extends NodeInterface
+{
+ /**
+ * Sets the name of the node.
+ */
+ public function setName(string $name);
+}
diff --git a/vendor/symfony/config/Definition/PrototypedArrayNode.php b/vendor/symfony/config/Definition/PrototypedArrayNode.php
new file mode 100644
index 0000000..e78159c
--- /dev/null
+++ b/vendor/symfony/config/Definition/PrototypedArrayNode.php
@@ -0,0 +1,350 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\DuplicateKeyException;
+use Symfony\Component\Config\Definition\Exception\Exception;
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\UnsetKeyException;
+
+/**
+ * Represents a prototyped Array node in the config tree.
+ *
+ * @author Johannes M. Schmitt
+ */
+class PrototypedArrayNode extends ArrayNode
+{
+ protected $prototype;
+ protected $keyAttribute;
+ protected $removeKeyAttribute = false;
+ protected $minNumberOfElements = 0;
+ protected $defaultValue = [];
+ protected $defaultChildren;
+ /**
+ * @var NodeInterface[] An array of the prototypes of the simplified value children
+ */
+ private $valuePrototypes = [];
+
+ /**
+ * Sets the minimum number of elements that a prototype based node must
+ * contain. By default this is zero, meaning no elements.
+ */
+ public function setMinNumberOfElements(int $number)
+ {
+ $this->minNumberOfElements = $number;
+ }
+
+ /**
+ * Sets the attribute which value is to be used as key.
+ *
+ * This is useful when you have an indexed array that should be an
+ * associative array. You can select an item from within the array
+ * to be the key of the particular item. For example, if "id" is the
+ * "key", then:
+ *
+ * [
+ * ['id' => 'my_name', 'foo' => 'bar'],
+ * ];
+ *
+ * becomes
+ *
+ * [
+ * 'my_name' => ['foo' => 'bar'],
+ * ];
+ *
+ * If you'd like "'id' => 'my_name'" to still be present in the resulting
+ * array, then you can set the second argument of this method to false.
+ *
+ * @param string $attribute The name of the attribute which value is to be used as a key
+ * @param bool $remove Whether or not to remove the key
+ */
+ public function setKeyAttribute(string $attribute, bool $remove = true)
+ {
+ $this->keyAttribute = $attribute;
+ $this->removeKeyAttribute = $remove;
+ }
+
+ /**
+ * Retrieves the name of the attribute which value should be used as key.
+ *
+ * @return string|null
+ */
+ public function getKeyAttribute()
+ {
+ return $this->keyAttribute;
+ }
+
+ /**
+ * Sets the default value of this node.
+ */
+ public function setDefaultValue(array $value)
+ {
+ $this->defaultValue = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDefaultValue()
+ {
+ return true;
+ }
+
+ /**
+ * Adds default children when none are set.
+ *
+ * @param int|string|array|null $children The number of children|The child name|The children names to be added
+ */
+ public function setAddChildrenIfNoneSet($children = ['defaults'])
+ {
+ if (null === $children) {
+ $this->defaultChildren = ['defaults'];
+ } else {
+ $this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * The default value could be either explicited or derived from the prototype
+ * default value.
+ */
+ public function getDefaultValue()
+ {
+ if (null !== $this->defaultChildren) {
+ $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : [];
+ $defaults = [];
+ foreach (array_values($this->defaultChildren) as $i => $name) {
+ $defaults[null === $this->keyAttribute ? $i : $name] = $default;
+ }
+
+ return $defaults;
+ }
+
+ return $this->defaultValue;
+ }
+
+ /**
+ * Sets the node prototype.
+ */
+ public function setPrototype(PrototypeNodeInterface $node)
+ {
+ $this->prototype = $node;
+ }
+
+ /**
+ * Retrieves the prototype.
+ *
+ * @return PrototypeNodeInterface
+ */
+ public function getPrototype()
+ {
+ return $this->prototype;
+ }
+
+ /**
+ * Disable adding concrete children for prototyped nodes.
+ *
+ * @throws Exception
+ */
+ public function addChild(NodeInterface $node)
+ {
+ throw new Exception('A prototyped array node cannot have concrete children.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function finalizeValue($value)
+ {
+ if (false === $value) {
+ throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value)));
+ }
+
+ foreach ($value as $k => $v) {
+ $prototype = $this->getPrototypeForChild($k);
+ try {
+ $value[$k] = $prototype->finalize($v);
+ } catch (UnsetKeyException $e) {
+ unset($value[$k]);
+ }
+ }
+
+ if (\count($value) < $this->minNumberOfElements) {
+ $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws DuplicateKeyException
+ */
+ protected function normalizeValue($value)
+ {
+ if (false === $value) {
+ return $value;
+ }
+
+ $value = $this->remapXml($value);
+
+ $isList = array_is_list($value);
+ $normalized = [];
+ foreach ($value as $k => $v) {
+ if (null !== $this->keyAttribute && \is_array($v)) {
+ if (!isset($v[$this->keyAttribute]) && \is_int($k) && $isList) {
+ $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ } elseif (isset($v[$this->keyAttribute])) {
+ $k = $v[$this->keyAttribute];
+
+ if (\is_float($k)) {
+ $k = var_export($k, true);
+ }
+
+ // remove the key attribute when required
+ if ($this->removeKeyAttribute) {
+ unset($v[$this->keyAttribute]);
+ }
+
+ // if only "value" is left
+ if (array_keys($v) === ['value']) {
+ $v = $v['value'];
+ if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && \array_key_exists('value', $children)) {
+ $valuePrototype = current($this->valuePrototypes) ?: clone $children['value'];
+ $valuePrototype->parent = $this;
+ $originalClosures = $this->prototype->normalizationClosures;
+ if (\is_array($originalClosures)) {
+ $valuePrototypeClosures = $valuePrototype->normalizationClosures;
+ $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures;
+ }
+ $this->valuePrototypes[$k] = $valuePrototype;
+ }
+ }
+ }
+
+ if (\array_key_exists($k, $normalized)) {
+ $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ $prototype = $this->getPrototypeForChild($k);
+ if (null !== $this->keyAttribute || !$isList) {
+ $normalized[$k] = $prototype->normalize($v);
+ } else {
+ $normalized[] = $prototype->normalize($v);
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function mergeValues($leftSide, $rightSide)
+ {
+ if (false === $rightSide) {
+ // if this is still false after the last config has been merged the
+ // finalization pass will take care of removing this key entirely
+ return false;
+ }
+
+ if (false === $leftSide || !$this->performDeepMerging) {
+ return $rightSide;
+ }
+
+ $isList = array_is_list($rightSide);
+ foreach ($rightSide as $k => $v) {
+ // prototype, and key is irrelevant there are no named keys, append the element
+ if (null === $this->keyAttribute && $isList) {
+ $leftSide[] = $v;
+ continue;
+ }
+
+ // no conflict
+ if (!\array_key_exists($k, $leftSide)) {
+ if (!$this->allowNewKeys) {
+ $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath()));
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ $leftSide[$k] = $v;
+ continue;
+ }
+
+ $prototype = $this->getPrototypeForChild($k);
+ $leftSide[$k] = $prototype->merge($leftSide[$k], $v);
+ }
+
+ return $leftSide;
+ }
+
+ /**
+ * Returns a prototype for the child node that is associated to $key in the value array.
+ * For general child nodes, this will be $this->prototype.
+ * But if $this->removeKeyAttribute is true and there are only two keys in the child node:
+ * one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
+ *
+ * For example, assume $this->keyAttribute is 'name' and the value array is as follows:
+ *
+ * [
+ * [
+ * 'name' => 'name001',
+ * 'value' => 'value001'
+ * ]
+ * ]
+ *
+ * Now, the key is 0 and the child node is:
+ *
+ * [
+ * 'name' => 'name001',
+ * 'value' => 'value001'
+ * ]
+ *
+ * When normalizing the value array, the 'name' element will removed from the child node
+ * and its value becomes the new key of the child node:
+ *
+ * [
+ * 'name001' => ['value' => 'value001']
+ * ]
+ *
+ * Now only 'value' element is left in the child node which can be further simplified into a string:
+ *
+ * ['name001' => 'value001']
+ *
+ * Now, the key becomes 'name001' and the child node becomes 'value001' and
+ * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
+ *
+ * @return mixed
+ */
+ private function getPrototypeForChild(string $key)
+ {
+ $prototype = $this->valuePrototypes[$key] ?? $this->prototype;
+ $prototype->setName($key);
+
+ return $prototype;
+ }
+}
diff --git a/vendor/symfony/config/Definition/ScalarNode.php b/vendor/symfony/config/Definition/ScalarNode.php
new file mode 100644
index 0000000..5296c27
--- /dev/null
+++ b/vendor/symfony/config/Definition/ScalarNode.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
+
+/**
+ * This node represents a scalar value in the config tree.
+ *
+ * The following values are considered scalars:
+ * * booleans
+ * * strings
+ * * null
+ * * integers
+ * * floats
+ *
+ * @author Johannes M. Schmitt
+ */
+class ScalarNode extends VariableNode
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ if (!is_scalar($value) && null !== $value) {
+ $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function isValueEmpty($value)
+ {
+ // assume environment variables are never empty (which in practice is likely to be true during runtime)
+ // not doing so breaks many configs that are valid today
+ if ($this->isHandlingPlaceholder()) {
+ return false;
+ }
+
+ return null === $value || '' === $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getValidPlaceholderTypes(): array
+ {
+ return ['bool', 'int', 'float', 'string'];
+ }
+}
diff --git a/vendor/symfony/config/Definition/VariableNode.php b/vendor/symfony/config/Definition/VariableNode.php
new file mode 100644
index 0000000..e868ece
--- /dev/null
+++ b/vendor/symfony/config/Definition/VariableNode.php
@@ -0,0 +1,142 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Definition;
+
+use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+
+/**
+ * This node represents a value of variable type in the config tree.
+ *
+ * This node is intended for values of arbitrary type.
+ * Any PHP type is accepted as a value.
+ *
+ * @author Jeremy Mikola
+ */
+class VariableNode extends BaseNode implements PrototypeNodeInterface
+{
+ protected $defaultValueSet = false;
+ protected $defaultValue;
+ protected $allowEmptyValue = true;
+
+ public function setDefaultValue($value)
+ {
+ $this->defaultValueSet = true;
+ $this->defaultValue = $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasDefaultValue()
+ {
+ return $this->defaultValueSet;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultValue()
+ {
+ $v = $this->defaultValue;
+
+ return $v instanceof \Closure ? $v() : $v;
+ }
+
+ /**
+ * Sets if this node is allowed to have an empty value.
+ *
+ * @param bool $boolean True if this entity will accept empty values
+ */
+ public function setAllowEmptyValue(bool $boolean)
+ {
+ $this->allowEmptyValue = $boolean;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function validateType($value)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function finalizeValue($value)
+ {
+ // deny environment variables only when using custom validators
+ // this avoids ever passing an empty value to final validation closures
+ if (!$this->allowEmptyValue && $this->isHandlingPlaceholder() && $this->finalValidationClosures) {
+ $e = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath()));
+ if ($hint = $this->getInfo()) {
+ $e->addHint($hint);
+ }
+ $e->setPath($this->getPath());
+
+ throw $e;
+ }
+
+ if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
+ $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value)));
+ if ($hint = $this->getInfo()) {
+ $ex->addHint($hint);
+ }
+ $ex->setPath($this->getPath());
+
+ throw $ex;
+ }
+
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function normalizeValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function mergeValues($leftSide, $rightSide)
+ {
+ return $rightSide;
+ }
+
+ /**
+ * Evaluates if the given value is to be treated as empty.
+ *
+ * By default, PHP's empty() function is used to test for emptiness. This
+ * method may be overridden by subtypes to better match their understanding
+ * of empty data.
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ *
+ * @see finalizeValue()
+ */
+ protected function isValueEmpty($value)
+ {
+ return empty($value);
+ }
+}
diff --git a/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php
new file mode 100644
index 0000000..e235ea0
--- /dev/null
+++ b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Exception;
+
+/**
+ * Exception class for when a circular reference is detected when importing resources.
+ *
+ * @author Fabien Potencier
+ */
+class FileLoaderImportCircularReferenceException extends LoaderLoadException
+{
+ public function __construct(array $resources, ?int $code = 0, \Throwable $previous = null)
+ {
+ if (null === $code) {
+ trigger_deprecation('symfony/config', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__);
+
+ $code = 0;
+ }
+
+ $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]);
+
+ \Exception::__construct($message, $code, $previous);
+ }
+}
diff --git a/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php b/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php
new file mode 100644
index 0000000..3ee4b93
--- /dev/null
+++ b/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.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\Config\Exception;
+
+/**
+ * File locator exception if a file does not exist.
+ *
+ * @author Leo Feyer
+ */
+class FileLocatorFileNotFoundException extends \InvalidArgumentException
+{
+ private $paths;
+
+ public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, array $paths = [])
+ {
+ parent::__construct($message, $code, $previous);
+
+ $this->paths = $paths;
+ }
+
+ public function getPaths()
+ {
+ return $this->paths;
+ }
+}
diff --git a/vendor/symfony/config/Exception/LoaderLoadException.php b/vendor/symfony/config/Exception/LoaderLoadException.php
new file mode 100644
index 0000000..b20e74d
--- /dev/null
+++ b/vendor/symfony/config/Exception/LoaderLoadException.php
@@ -0,0 +1,115 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Exception;
+
+/**
+ * Exception class for when a resource cannot be loaded or imported.
+ *
+ * @author Ryan Weaver
+ */
+class LoaderLoadException extends \Exception
+{
+ /**
+ * @param string $resource The resource that could not be imported
+ * @param string|null $sourceResource The original resource importing the new resource
+ * @param int|null $code The error code
+ * @param \Throwable|null $previous A previous exception
+ * @param string|null $type The type of resource
+ */
+ public function __construct(string $resource, string $sourceResource = null, ?int $code = 0, \Throwable $previous = null, string $type = null)
+ {
+ if (null === $code) {
+ trigger_deprecation('symfony/config', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__);
+
+ $code = 0;
+ }
+
+ $message = '';
+ if ($previous) {
+ // Include the previous exception, to help the user see what might be the underlying cause
+
+ // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim...
+ if ('.' === substr($previous->getMessage(), -1)) {
+ $trimmedMessage = substr($previous->getMessage(), 0, -1);
+ $message .= sprintf('%s', $trimmedMessage).' in ';
+ } else {
+ $message .= sprintf('%s', $previous->getMessage()).' in ';
+ }
+ $message .= $resource.' ';
+
+ // show tweaked trace to complete the human readable sentence
+ if (null === $sourceResource) {
+ $message .= sprintf('(which is loaded in resource "%s")', $resource);
+ } else {
+ $message .= sprintf('(which is being imported from "%s")', $sourceResource);
+ }
+ $message .= '.';
+
+ // if there's no previous message, present it the default way
+ } elseif (null === $sourceResource) {
+ $message .= sprintf('Cannot load resource "%s".', $resource);
+ } else {
+ $message .= sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource);
+ }
+
+ // Is the resource located inside a bundle?
+ if ('@' === $resource[0]) {
+ $parts = explode(\DIRECTORY_SEPARATOR, $resource);
+ $bundle = substr($parts[0], 1);
+ $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle);
+ $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource);
+ } elseif (null !== $type) {
+ // maybe there is no loader for this specific type
+ if ('annotation' === $type) {
+ $message .= ' Make sure to use PHP 8+ or that annotations are installed and enabled.';
+ } else {
+ $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type);
+ }
+ }
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ protected function varToString($var)
+ {
+ if (\is_object($var)) {
+ return sprintf('Object(%s)', \get_class($var));
+ }
+
+ if (\is_array($var)) {
+ $a = [];
+ foreach ($var as $k => $v) {
+ $a[] = sprintf('%s => %s', $k, $this->varToString($v));
+ }
+
+ return sprintf('Array(%s)', implode(', ', $a));
+ }
+
+ if (\is_resource($var)) {
+ return sprintf('Resource(%s)', get_resource_type($var));
+ }
+
+ if (null === $var) {
+ return 'null';
+ }
+
+ if (false === $var) {
+ return 'false';
+ }
+
+ if (true === $var) {
+ return 'true';
+ }
+
+ return (string) $var;
+ }
+}
diff --git a/vendor/symfony/config/FileLocator.php b/vendor/symfony/config/FileLocator.php
new file mode 100644
index 0000000..da35090
--- /dev/null
+++ b/vendor/symfony/config/FileLocator.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
+
+/**
+ * FileLocator uses an array of pre-defined paths to find files.
+ *
+ * @author Fabien Potencier
+ */
+class FileLocator implements FileLocatorInterface
+{
+ protected $paths;
+
+ /**
+ * @param string|string[] $paths A path or an array of paths where to look for resources
+ */
+ public function __construct($paths = [])
+ {
+ $this->paths = (array) $paths;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function locate(string $name, string $currentPath = null, bool $first = true)
+ {
+ if ('' === $name) {
+ throw new \InvalidArgumentException('An empty file name is not valid to be located.');
+ }
+
+ if ($this->isAbsolutePath($name)) {
+ if (!file_exists($name)) {
+ throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, [$name]);
+ }
+
+ return $name;
+ }
+
+ $paths = $this->paths;
+
+ if (null !== $currentPath) {
+ array_unshift($paths, $currentPath);
+ }
+
+ $paths = array_unique($paths);
+ $filepaths = $notfound = [];
+
+ foreach ($paths as $path) {
+ if (@file_exists($file = $path.\DIRECTORY_SEPARATOR.$name)) {
+ if (true === $first) {
+ return $file;
+ }
+ $filepaths[] = $file;
+ } else {
+ $notfound[] = $file;
+ }
+ }
+
+ if (!$filepaths) {
+ throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: "%s").', $name, implode('", "', $paths)), 0, null, $notfound);
+ }
+
+ return $filepaths;
+ }
+
+ /**
+ * Returns whether the file path is an absolute path.
+ */
+ private function isAbsolutePath(string $file): bool
+ {
+ if ('/' === $file[0] || '\\' === $file[0]
+ || (\strlen($file) > 3 && ctype_alpha($file[0])
+ && ':' === $file[1]
+ && ('\\' === $file[2] || '/' === $file[2])
+ )
+ || null !== parse_url($file, \PHP_URL_SCHEME)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/symfony/config/FileLocatorInterface.php b/vendor/symfony/config/FileLocatorInterface.php
new file mode 100644
index 0000000..e3ca1d4
--- /dev/null
+++ b/vendor/symfony/config/FileLocatorInterface.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\Config;
+
+use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
+
+/**
+ * @author Fabien Potencier
+ */
+interface FileLocatorInterface
+{
+ /**
+ * Returns a full path for a given file name.
+ *
+ * @param string $name The file name to locate
+ * @param string|null $currentPath The current path
+ * @param bool $first Whether to return the first occurrence or an array of filenames
+ *
+ * @return string|array The full path to the file or an array of file paths
+ *
+ * @throws \InvalidArgumentException If $name is empty
+ * @throws FileLocatorFileNotFoundException If a file is not found
+ */
+ public function locate(string $name, string $currentPath = null, bool $first = true);
+}
diff --git a/vendor/symfony/config/LICENSE b/vendor/symfony/config/LICENSE
new file mode 100644
index 0000000..88bf75b
--- /dev/null
+++ b/vendor/symfony/config/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/config/Loader/DelegatingLoader.php b/vendor/symfony/config/Loader/DelegatingLoader.php
new file mode 100644
index 0000000..e5a74ee
--- /dev/null
+++ b/vendor/symfony/config/Loader/DelegatingLoader.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+use Symfony\Component\Config\Exception\LoaderLoadException;
+
+/**
+ * DelegatingLoader delegates loading to other loaders using a loader resolver.
+ *
+ * This loader acts as an array of LoaderInterface objects - each having
+ * a chance to load a given resource (handled by the resolver)
+ *
+ * @author Fabien Potencier
+ */
+class DelegatingLoader extends Loader
+{
+ public function __construct(LoaderResolverInterface $resolver)
+ {
+ $this->resolver = $resolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, string $type = null)
+ {
+ if (false === $loader = $this->resolver->resolve($resource, $type)) {
+ throw new LoaderLoadException($resource, null, 0, null, $type);
+ }
+
+ return $loader->load($resource, $type);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, string $type = null)
+ {
+ return false !== $this->resolver->resolve($resource, $type);
+ }
+}
diff --git a/vendor/symfony/config/Loader/FileLoader.php b/vendor/symfony/config/Loader/FileLoader.php
new file mode 100644
index 0000000..4e1b46c
--- /dev/null
+++ b/vendor/symfony/config/Loader/FileLoader.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException;
+use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
+use Symfony\Component\Config\Exception\LoaderLoadException;
+use Symfony\Component\Config\FileLocatorInterface;
+use Symfony\Component\Config\Resource\FileExistenceResource;
+use Symfony\Component\Config\Resource\GlobResource;
+
+/**
+ * FileLoader is the abstract class used by all built-in loaders that are file based.
+ *
+ * @author Fabien Potencier
+ */
+abstract class FileLoader extends Loader
+{
+ protected static $loading = [];
+
+ protected $locator;
+
+ private $currentDir;
+
+ public function __construct(FileLocatorInterface $locator, string $env = null)
+ {
+ $this->locator = $locator;
+ parent::__construct($env);
+ }
+
+ /**
+ * Sets the current directory.
+ */
+ public function setCurrentDir(string $dir)
+ {
+ $this->currentDir = $dir;
+ }
+
+ /**
+ * Returns the file locator used by this loader.
+ *
+ * @return FileLocatorInterface
+ */
+ public function getLocator()
+ {
+ return $this->locator;
+ }
+
+ /**
+ * Imports a resource.
+ *
+ * @param mixed $resource A Resource
+ * @param string|null $type The resource type or null if unknown
+ * @param bool $ignoreErrors Whether to ignore import errors or not
+ * @param string|null $sourceResource The original resource importing the new resource
+ * @param string|string[]|null $exclude Glob patterns to exclude from the import
+ *
+ * @return mixed
+ *
+ * @throws LoaderLoadException
+ * @throws FileLoaderImportCircularReferenceException
+ * @throws FileLocatorFileNotFoundException
+ */
+ public function import($resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null, $exclude = null)
+ {
+ if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) {
+ $excluded = [];
+ foreach ((array) $exclude as $pattern) {
+ foreach ($this->glob($pattern, true, $_, false, true) as $path => $info) {
+ // normalize Windows slashes and remove trailing slashes
+ $excluded[rtrim(str_replace('\\', '/', $path), '/')] = true;
+ }
+ }
+
+ $ret = [];
+ $isSubpath = 0 !== $i && str_contains(substr($resource, 0, $i), '/');
+ foreach ($this->glob($resource, false, $_, $ignoreErrors || !$isSubpath, false, $excluded) as $path => $info) {
+ if (null !== $res = $this->doImport($path, 'glob' === $type ? null : $type, $ignoreErrors, $sourceResource)) {
+ $ret[] = $res;
+ }
+ $isSubpath = true;
+ }
+
+ if ($isSubpath) {
+ return isset($ret[1]) ? $ret : ($ret[0] ?? null);
+ }
+ }
+
+ return $this->doImport($resource, $type, $ignoreErrors, $sourceResource);
+ }
+
+ /**
+ * @internal
+ */
+ protected function glob(string $pattern, bool $recursive, &$resource = null, bool $ignoreErrors = false, bool $forExclusion = false, array $excluded = [])
+ {
+ if (\strlen($pattern) === $i = strcspn($pattern, '*?{[')) {
+ $prefix = $pattern;
+ $pattern = '';
+ } elseif (0 === $i || !str_contains(substr($pattern, 0, $i), '/')) {
+ $prefix = '.';
+ $pattern = '/'.$pattern;
+ } else {
+ $prefix = \dirname(substr($pattern, 0, 1 + $i));
+ $pattern = substr($pattern, \strlen($prefix));
+ }
+
+ try {
+ $prefix = $this->locator->locate($prefix, $this->currentDir, true);
+ } catch (FileLocatorFileNotFoundException $e) {
+ if (!$ignoreErrors) {
+ throw $e;
+ }
+
+ $resource = [];
+ foreach ($e->getPaths() as $path) {
+ $resource[] = new FileExistenceResource($path);
+ }
+
+ return;
+ }
+ $resource = new GlobResource($prefix, $pattern, $recursive, $forExclusion, $excluded);
+
+ yield from $resource;
+ }
+
+ private function doImport($resource, string $type = null, bool $ignoreErrors = false, string $sourceResource = null)
+ {
+ try {
+ $loader = $this->resolve($resource, $type);
+
+ if ($loader instanceof self && null !== $this->currentDir) {
+ $resource = $loader->getLocator()->locate($resource, $this->currentDir, false);
+ }
+
+ $resources = \is_array($resource) ? $resource : [$resource];
+ for ($i = 0; $i < $resourcesCount = \count($resources); ++$i) {
+ if (isset(self::$loading[$resources[$i]])) {
+ if ($i == $resourcesCount - 1) {
+ throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading));
+ }
+ } else {
+ $resource = $resources[$i];
+ break;
+ }
+ }
+ self::$loading[$resource] = true;
+
+ try {
+ $ret = $loader->load($resource, $type);
+ } finally {
+ unset(self::$loading[$resource]);
+ }
+
+ return $ret;
+ } catch (FileLoaderImportCircularReferenceException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ if (!$ignoreErrors) {
+ // prevent embedded imports from nesting multiple exceptions
+ if ($e instanceof LoaderLoadException) {
+ throw $e;
+ }
+
+ throw new LoaderLoadException($resource, $sourceResource, 0, $e, $type);
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/symfony/config/Loader/GlobFileLoader.php b/vendor/symfony/config/Loader/GlobFileLoader.php
new file mode 100644
index 0000000..fecb1c5
--- /dev/null
+++ b/vendor/symfony/config/Loader/GlobFileLoader.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * GlobFileLoader loads files from a glob pattern.
+ *
+ * @author Fabien Potencier
+ */
+class GlobFileLoader extends FileLoader
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, string $type = null)
+ {
+ return $this->import($resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports($resource, string $type = null)
+ {
+ return 'glob' === $type;
+ }
+}
diff --git a/vendor/symfony/config/Loader/Loader.php b/vendor/symfony/config/Loader/Loader.php
new file mode 100644
index 0000000..e7d74b5
--- /dev/null
+++ b/vendor/symfony/config/Loader/Loader.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+use Symfony\Component\Config\Exception\LoaderLoadException;
+
+/**
+ * Loader is the abstract class used by all built-in loaders.
+ *
+ * @author Fabien Potencier
+ */
+abstract class Loader implements LoaderInterface
+{
+ protected $resolver;
+ protected $env;
+
+ public function __construct(string $env = null)
+ {
+ $this->env = $env;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getResolver()
+ {
+ return $this->resolver;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setResolver(LoaderResolverInterface $resolver)
+ {
+ $this->resolver = $resolver;
+ }
+
+ /**
+ * Imports a resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return mixed
+ */
+ public function import($resource, string $type = null)
+ {
+ return $this->resolve($resource, $type)->load($resource, $type);
+ }
+
+ /**
+ * Finds a loader able to load an imported resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return LoaderInterface
+ *
+ * @throws LoaderLoadException If no loader is found
+ */
+ public function resolve($resource, string $type = null)
+ {
+ if ($this->supports($resource, $type)) {
+ return $this;
+ }
+
+ $loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type);
+
+ if (false === $loader) {
+ throw new LoaderLoadException($resource, null, 0, null, $type);
+ }
+
+ return $loader;
+ }
+}
diff --git a/vendor/symfony/config/Loader/LoaderInterface.php b/vendor/symfony/config/Loader/LoaderInterface.php
new file mode 100644
index 0000000..93a160b
--- /dev/null
+++ b/vendor/symfony/config/Loader/LoaderInterface.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\Config\Loader;
+
+/**
+ * LoaderInterface is the interface implemented by all loader classes.
+ *
+ * @author Fabien Potencier
+ */
+interface LoaderInterface
+{
+ /**
+ * Loads a resource.
+ *
+ * @param mixed $resource The resource
+ *
+ * @return mixed
+ *
+ * @throws \Exception If something went wrong
+ */
+ public function load($resource, string $type = null);
+
+ /**
+ * Returns whether this class supports the given resource.
+ *
+ * @param mixed $resource A resource
+ *
+ * @return bool
+ */
+ public function supports($resource, string $type = null);
+
+ /**
+ * Gets the loader resolver.
+ *
+ * @return LoaderResolverInterface
+ */
+ public function getResolver();
+
+ /**
+ * Sets the loader resolver.
+ */
+ public function setResolver(LoaderResolverInterface $resolver);
+}
diff --git a/vendor/symfony/config/Loader/LoaderResolver.php b/vendor/symfony/config/Loader/LoaderResolver.php
new file mode 100644
index 0000000..cce0702
--- /dev/null
+++ b/vendor/symfony/config/Loader/LoaderResolver.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * LoaderResolver selects a loader for a given resource.
+ *
+ * A resource can be anything (e.g. a full path to a config file or a Closure).
+ * Each loader determines whether it can load a resource and how.
+ *
+ * @author Fabien Potencier
+ */
+class LoaderResolver implements LoaderResolverInterface
+{
+ /**
+ * @var LoaderInterface[] An array of LoaderInterface objects
+ */
+ private $loaders = [];
+
+ /**
+ * @param LoaderInterface[] $loaders An array of loaders
+ */
+ public function __construct(array $loaders = [])
+ {
+ foreach ($loaders as $loader) {
+ $this->addLoader($loader);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve($resource, string $type = null)
+ {
+ foreach ($this->loaders as $loader) {
+ if ($loader->supports($resource, $type)) {
+ return $loader;
+ }
+ }
+
+ return false;
+ }
+
+ public function addLoader(LoaderInterface $loader)
+ {
+ $this->loaders[] = $loader;
+ $loader->setResolver($this);
+ }
+
+ /**
+ * Returns the registered loaders.
+ *
+ * @return LoaderInterface[]
+ */
+ public function getLoaders()
+ {
+ return $this->loaders;
+ }
+}
diff --git a/vendor/symfony/config/Loader/LoaderResolverInterface.php b/vendor/symfony/config/Loader/LoaderResolverInterface.php
new file mode 100644
index 0000000..8a48419
--- /dev/null
+++ b/vendor/symfony/config/Loader/LoaderResolverInterface.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\Config\Loader;
+
+/**
+ * LoaderResolverInterface selects a loader for a given resource.
+ *
+ * @author Fabien Potencier
+ */
+interface LoaderResolverInterface
+{
+ /**
+ * Returns a loader able to load the resource.
+ *
+ * @param mixed $resource A resource
+ * @param string|null $type The resource type or null if unknown
+ *
+ * @return LoaderInterface|false
+ */
+ public function resolve($resource, string $type = null);
+}
diff --git a/vendor/symfony/config/Loader/ParamConfigurator.php b/vendor/symfony/config/Loader/ParamConfigurator.php
new file mode 100644
index 0000000..70c3f79
--- /dev/null
+++ b/vendor/symfony/config/Loader/ParamConfigurator.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Loader;
+
+/**
+ * Placeholder for a parameter.
+ *
+ * @author Tobias Nyholm
+ */
+class ParamConfigurator
+{
+ private $name;
+
+ public function __construct(string $name)
+ {
+ $this->name = $name;
+ }
+
+ public function __toString(): string
+ {
+ return '%'.$this->name.'%';
+ }
+}
diff --git a/vendor/symfony/config/README.md b/vendor/symfony/config/README.md
new file mode 100644
index 0000000..10c2ddd
--- /dev/null
+++ b/vendor/symfony/config/README.md
@@ -0,0 +1,15 @@
+Config Component
+================
+
+The Config component helps find, load, combine, autofill and validate
+configuration values of any kind, whatever their source may be (YAML, XML, INI
+files, or for instance a database).
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/config.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/vendor/symfony/config/Resource/ClassExistenceResource.php b/vendor/symfony/config/Resource/ClassExistenceResource.php
new file mode 100644
index 0000000..6aff151
--- /dev/null
+++ b/vendor/symfony/config/Resource/ClassExistenceResource.php
@@ -0,0 +1,232 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * ClassExistenceResource represents a class existence.
+ * Freshness is only evaluated against resource existence.
+ *
+ * The resource must be a fully-qualified class name.
+ *
+ * @author Fabien Potencier
+ *
+ * @final
+ */
+class ClassExistenceResource implements SelfCheckingResourceInterface
+{
+ private $resource;
+ private $exists;
+
+ private static $autoloadLevel = 0;
+ private static $autoloadedClass;
+ private static $existsCache = [];
+
+ /**
+ * @param string $resource The fully-qualified class name
+ * @param bool|null $exists Boolean when the existency check has already been done
+ */
+ public function __construct(string $resource, bool $exists = null)
+ {
+ $this->resource = $resource;
+ if (null !== $exists) {
+ $this->exists = [$exists, null];
+ }
+ }
+
+ public function __toString(): string
+ {
+ return $this->resource;
+ }
+
+ public function getResource(): string
+ {
+ return $this->resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \ReflectionException when a parent class/interface/trait is not found
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
+
+ if (null !== $exists = &self::$existsCache[$this->resource]) {
+ if ($loaded) {
+ $exists = [true, null];
+ } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
+ throw new \ReflectionException($exists[1]);
+ }
+ } elseif ([false, null] === $exists = [$loaded, null]) {
+ if (!self::$autoloadLevel++) {
+ spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
+ }
+ $autoloadedClass = self::$autoloadedClass;
+ self::$autoloadedClass = ltrim($this->resource, '\\');
+
+ try {
+ $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
+ } catch (\Exception $e) {
+ $exists[1] = $e->getMessage();
+
+ try {
+ self::throwOnRequiredClass($this->resource, $e);
+ } catch (\ReflectionException $e) {
+ if (0 >= $timestamp) {
+ throw $e;
+ }
+ }
+ } catch (\Throwable $e) {
+ $exists[1] = $e->getMessage();
+
+ throw $e;
+ } finally {
+ self::$autoloadedClass = $autoloadedClass;
+ if (!--self::$autoloadLevel) {
+ spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
+ }
+ }
+ }
+
+ if (null === $this->exists) {
+ $this->exists = $exists;
+ }
+
+ return $this->exists[0] xor !$exists[0];
+ }
+
+ /**
+ * @internal
+ */
+ public function __sleep(): array
+ {
+ if (null === $this->exists) {
+ $this->isFresh(0);
+ }
+
+ return ['resource', 'exists'];
+ }
+
+ /**
+ * @internal
+ */
+ public function __wakeup()
+ {
+ if (\is_bool($this->exists)) {
+ $this->exists = [$this->exists, null];
+ }
+ }
+
+ /**
+ * Throws a reflection exception when the passed class does not exist but is required.
+ *
+ * A class is considered "not required" when it's loaded as part of a "class_exists" or similar check.
+ *
+ * This function can be used as an autoload function to throw a reflection
+ * exception if the class was not found by previous autoload functions.
+ *
+ * A previous exception can be passed. In this case, the class is considered as being
+ * required totally, so if it doesn't exist, a reflection exception is always thrown.
+ * If it exists, the previous exception is rethrown.
+ *
+ * @throws \ReflectionException
+ *
+ * @internal
+ */
+ public static function throwOnRequiredClass(string $class, \Exception $previous = null)
+ {
+ // If the passed class is the resource being checked, we shouldn't throw.
+ if (null === $previous && self::$autoloadedClass === $class) {
+ return;
+ }
+
+ if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {
+ if (null !== $previous) {
+ throw $previous;
+ }
+
+ return;
+ }
+
+ if ($previous instanceof \ReflectionException) {
+ throw $previous;
+ }
+
+ $message = sprintf('Class "%s" not found.', $class);
+
+ if (self::$autoloadedClass !== $class) {
+ $message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
+ }
+
+ if (null !== $previous) {
+ $message = $previous->getMessage();
+ }
+
+ $e = new \ReflectionException($message, 0, $previous);
+
+ if (null !== $previous) {
+ throw $e;
+ }
+
+ $trace = debug_backtrace();
+ $autoloadFrame = [
+ 'function' => 'spl_autoload_call',
+ 'args' => [$class],
+ ];
+
+ if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) {
+ $callerFrame = $trace[1];
+ $i = 2;
+ } 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;
+ }
+
+ $props = [
+ 'file' => $callerFrame['file'] ?? null,
+ 'line' => $callerFrame['line'] ?? null,
+ 'trace' => \array_slice($trace, 1 + $i),
+ ];
+
+ foreach ($props as $p => $v) {
+ if (null !== $v) {
+ $r = new \ReflectionProperty(\Exception::class, $p);
+ $r->setAccessible(true);
+ $r->setValue($e, $v);
+ }
+ }
+ }
+
+ throw $e;
+ }
+}
diff --git a/vendor/symfony/config/Resource/ComposerResource.php b/vendor/symfony/config/Resource/ComposerResource.php
new file mode 100644
index 0000000..f552f80
--- /dev/null
+++ b/vendor/symfony/config/Resource/ComposerResource.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * ComposerResource tracks the PHP version and Composer dependencies.
+ *
+ * @author Nicolas Grekas
+ *
+ * @final
+ */
+class ComposerResource implements SelfCheckingResourceInterface
+{
+ private $vendors;
+
+ private static $runtimeVendors;
+
+ public function __construct()
+ {
+ self::refresh();
+ $this->vendors = self::$runtimeVendors;
+ }
+
+ public function getVendors(): array
+ {
+ return array_keys($this->vendors);
+ }
+
+ public function __toString(): string
+ {
+ return __CLASS__;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ self::refresh();
+
+ return array_values(self::$runtimeVendors) === array_values($this->vendors);
+ }
+
+ private static function refresh()
+ {
+ self::$runtimeVendors = [];
+
+ foreach (get_declared_classes() as $class) {
+ if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) {
+ $r = new \ReflectionClass($class);
+ $v = \dirname($r->getFileName(), 2);
+ if (is_file($v.'/composer/installed.json')) {
+ self::$runtimeVendors[$v] = @filemtime($v.'/composer/installed.json');
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/config/Resource/DirectoryResource.php b/vendor/symfony/config/Resource/DirectoryResource.php
new file mode 100644
index 0000000..035814a
--- /dev/null
+++ b/vendor/symfony/config/Resource/DirectoryResource.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\Config\Resource;
+
+/**
+ * DirectoryResource represents a resources stored in a subdirectory tree.
+ *
+ * @author Fabien Potencier
+ *
+ * @final
+ */
+class DirectoryResource implements SelfCheckingResourceInterface
+{
+ private $resource;
+ private $pattern;
+
+ /**
+ * @param string $resource The file path to the resource
+ * @param string|null $pattern A pattern to restrict monitored files
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(string $resource, string $pattern = null)
+ {
+ $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
+ $this->pattern = $pattern;
+
+ if (false === $this->resource || !is_dir($this->resource)) {
+ throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource));
+ }
+ }
+
+ public function __toString(): string
+ {
+ return md5(serialize([$this->resource, $this->pattern]));
+ }
+
+ public function getResource(): string
+ {
+ return $this->resource;
+ }
+
+ public function getPattern(): ?string
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ if (!is_dir($this->resource)) {
+ return false;
+ }
+
+ if ($timestamp < filemtime($this->resource)) {
+ return false;
+ }
+
+ foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) {
+ // if regex filtering is enabled only check matching files
+ if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) {
+ continue;
+ }
+
+ // always monitor directories for changes, except the .. entries
+ // (otherwise deleted files wouldn't get detected)
+ if ($file->isDir() && str_ends_with($file, '/..')) {
+ continue;
+ }
+
+ // for broken links
+ try {
+ $fileMTime = $file->getMTime();
+ } catch (\RuntimeException $e) {
+ continue;
+ }
+
+ // early return if a file's mtime exceeds the passed timestamp
+ if ($timestamp < $fileMTime) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/symfony/config/Resource/FileExistenceResource.php b/vendor/symfony/config/Resource/FileExistenceResource.php
new file mode 100644
index 0000000..6d79d6d
--- /dev/null
+++ b/vendor/symfony/config/Resource/FileExistenceResource.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * FileExistenceResource represents a resource stored on the filesystem.
+ * Freshness is only evaluated against resource creation or deletion.
+ *
+ * The resource can be a file or a directory.
+ *
+ * @author Charles-Henri Bruyand
+ *
+ * @final
+ */
+class FileExistenceResource implements SelfCheckingResourceInterface
+{
+ private $resource;
+
+ private $exists;
+
+ /**
+ * @param string $resource The file path to the resource
+ */
+ public function __construct(string $resource)
+ {
+ $this->resource = $resource;
+ $this->exists = file_exists($resource);
+ }
+
+ public function __toString(): string
+ {
+ return $this->resource;
+ }
+
+ public function getResource(): string
+ {
+ return $this->resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ return file_exists($this->resource) === $this->exists;
+ }
+}
diff --git a/vendor/symfony/config/Resource/FileResource.php b/vendor/symfony/config/Resource/FileResource.php
new file mode 100644
index 0000000..ee6684c
--- /dev/null
+++ b/vendor/symfony/config/Resource/FileResource.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * FileResource represents a resource stored on the filesystem.
+ *
+ * The resource can be a file or a directory.
+ *
+ * @author Fabien Potencier
+ *
+ * @final
+ */
+class FileResource implements SelfCheckingResourceInterface
+{
+ /**
+ * @var string|false
+ */
+ private $resource;
+
+ /**
+ * @param string $resource The file path to the resource
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(string $resource)
+ {
+ $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false);
+
+ if (false === $this->resource) {
+ throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource));
+ }
+ }
+
+ public function __toString(): string
+ {
+ return $this->resource;
+ }
+
+ /**
+ * Returns the canonicalized, absolute path to the resource.
+ */
+ public function getResource(): string
+ {
+ return $this->resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ return false !== ($filemtime = @filemtime($this->resource)) && $filemtime <= $timestamp;
+ }
+}
diff --git a/vendor/symfony/config/Resource/GlobResource.php b/vendor/symfony/config/Resource/GlobResource.php
new file mode 100644
index 0000000..093f559
--- /dev/null
+++ b/vendor/symfony/config/Resource/GlobResource.php
@@ -0,0 +1,237 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Finder\Glob;
+
+/**
+ * GlobResource represents a set of resources stored on the filesystem.
+ *
+ * Only existence/removal is tracked (not mtimes.)
+ *
+ * @author Nicolas Grekas
+ *
+ * @final
+ *
+ * @implements \IteratorAggregate
+ */
+class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface
+{
+ private $prefix;
+ private $pattern;
+ private $recursive;
+ private $hash;
+ private $forExclusion;
+ private $excludedPrefixes;
+ private $globBrace;
+
+ /**
+ * @param string $prefix A directory prefix
+ * @param string $pattern A glob pattern
+ * @param bool $recursive Whether directories should be scanned recursively or not
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(string $prefix, string $pattern, bool $recursive, bool $forExclusion = false, array $excludedPrefixes = [])
+ {
+ ksort($excludedPrefixes);
+ $this->prefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false);
+ $this->pattern = $pattern;
+ $this->recursive = $recursive;
+ $this->forExclusion = $forExclusion;
+ $this->excludedPrefixes = $excludedPrefixes;
+ $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0;
+
+ if (false === $this->prefix) {
+ throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix));
+ }
+ }
+
+ public function getPrefix(): string
+ {
+ return $this->prefix;
+ }
+
+ public function __toString(): string
+ {
+ return 'glob.'.$this->prefix.(int) $this->recursive.$this->pattern.(int) $this->forExclusion.implode("\0", $this->excludedPrefixes);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ $hash = $this->computeHash();
+
+ if (null === $this->hash) {
+ $this->hash = $hash;
+ }
+
+ return $this->hash === $hash;
+ }
+
+ /**
+ * @internal
+ */
+ public function __sleep(): array
+ {
+ if (null === $this->hash) {
+ $this->hash = $this->computeHash();
+ }
+
+ return ['prefix', 'pattern', 'recursive', 'hash', 'forExclusion', 'excludedPrefixes'];
+ }
+
+ /**
+ * @internal
+ */
+ public function __wakeup(): void
+ {
+ $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0;
+ }
+
+ public function getIterator(): \Traversable
+ {
+ if (!file_exists($this->prefix) || (!$this->recursive && '' === $this->pattern)) {
+ return;
+ }
+ $prefix = str_replace('\\', '/', $this->prefix);
+ $paths = null;
+
+ if (!str_starts_with($this->prefix, 'phar://') && !str_contains($this->pattern, '/**/')) {
+ if ($this->globBrace || !str_contains($this->pattern, '{')) {
+ $paths = glob($this->prefix.$this->pattern, \GLOB_NOSORT | $this->globBrace);
+ } elseif (!str_contains($this->pattern, '\\') || !preg_match('/\\\\[,{}]/', $this->pattern)) {
+ foreach ($this->expandGlob($this->pattern) as $p) {
+ $paths[] = glob($this->prefix.$p, \GLOB_NOSORT);
+ }
+ $paths = array_merge(...$paths);
+ }
+ }
+
+ if (null !== $paths) {
+ natsort($paths);
+ foreach ($paths as $path) {
+ if ($this->excludedPrefixes) {
+ $normalizedPath = str_replace('\\', '/', $path);
+ do {
+ if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) {
+ continue 2;
+ }
+ } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath));
+ }
+
+ if (is_file($path)) {
+ yield $path => new \SplFileInfo($path);
+ }
+ if (!is_dir($path)) {
+ continue;
+ }
+ if ($this->forExclusion) {
+ yield $path => new \SplFileInfo($path);
+ continue;
+ }
+ if (!$this->recursive || isset($this->excludedPrefixes[str_replace('\\', '/', $path)])) {
+ continue;
+ }
+ $files = iterator_to_array(new \RecursiveIteratorIterator(
+ new \RecursiveCallbackFilterIterator(
+ new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
+ function (\SplFileInfo $file, $path) {
+ return !isset($this->excludedPrefixes[str_replace('\\', '/', $path)]) && '.' !== $file->getBasename()[0];
+ }
+ ),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ ));
+ uksort($files, 'strnatcmp');
+
+ foreach ($files as $path => $info) {
+ if ($info->isFile()) {
+ yield $path => $info;
+ }
+ }
+ }
+
+ return;
+ }
+
+ if (!class_exists(Finder::class)) {
+ throw new \LogicException(sprintf('Extended glob pattern "%s" cannot be used as the Finder component is not installed.', $this->pattern));
+ }
+
+ $finder = new Finder();
+ $regex = Glob::toRegex($this->pattern);
+ if ($this->recursive) {
+ $regex = substr_replace($regex, '(/|$)', -2, 1);
+ }
+
+ $prefixLen = \strlen($this->prefix);
+ foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) {
+ $normalizedPath = str_replace('\\', '/', $path);
+ if (!preg_match($regex, substr($normalizedPath, $prefixLen)) || !$info->isFile()) {
+ continue;
+ }
+ if ($this->excludedPrefixes) {
+ do {
+ if (isset($this->excludedPrefixes[$dirPath = $normalizedPath])) {
+ continue 2;
+ }
+ } while ($prefix !== $dirPath && $dirPath !== $normalizedPath = \dirname($dirPath));
+ }
+
+ yield $path => $info;
+ }
+ }
+
+ private function computeHash(): string
+ {
+ $hash = hash_init('md5');
+
+ foreach ($this->getIterator() as $path => $info) {
+ hash_update($hash, $path."\n");
+ }
+
+ return hash_final($hash);
+ }
+
+ private function expandGlob(string $pattern): array
+ {
+ $segments = preg_split('/\{([^{}]*+)\}/', $pattern, -1, \PREG_SPLIT_DELIM_CAPTURE);
+ $paths = [$segments[0]];
+ $patterns = [];
+
+ for ($i = 1; $i < \count($segments); $i += 2) {
+ $patterns = [];
+
+ foreach (explode(',', $segments[$i]) as $s) {
+ foreach ($paths as $p) {
+ $patterns[] = $p.$s.$segments[1 + $i];
+ }
+ }
+
+ $paths = $patterns;
+ }
+
+ $j = 0;
+ foreach ($patterns as $i => $p) {
+ if (str_contains($p, '{')) {
+ $p = $this->expandGlob($p);
+ array_splice($paths, $i + $j, 1, $p);
+ $j += \count($p) - 1;
+ }
+ }
+
+ return $paths;
+ }
+}
diff --git a/vendor/symfony/config/Resource/ReflectionClassResource.php b/vendor/symfony/config/Resource/ReflectionClassResource.php
new file mode 100644
index 0000000..06f1d76
--- /dev/null
+++ b/vendor/symfony/config/Resource/ReflectionClassResource.php
@@ -0,0 +1,267 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
+use Symfony\Contracts\Service\ServiceSubscriberInterface;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @final
+ */
+class ReflectionClassResource implements SelfCheckingResourceInterface
+{
+ private $files = [];
+ private $className;
+ private $classReflector;
+ private $excludedVendors = [];
+ private $hash;
+
+ public function __construct(\ReflectionClass $classReflector, array $excludedVendors = [])
+ {
+ $this->className = $classReflector->name;
+ $this->classReflector = $classReflector;
+ $this->excludedVendors = $excludedVendors;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isFresh(int $timestamp): bool
+ {
+ if (null === $this->hash) {
+ $this->hash = $this->computeHash();
+ $this->loadFiles($this->classReflector);
+ }
+
+ foreach ($this->files as $file => $v) {
+ if (false === $filemtime = @filemtime($file)) {
+ return false;
+ }
+
+ if ($filemtime > $timestamp) {
+ return $this->hash === $this->computeHash();
+ }
+ }
+
+ return true;
+ }
+
+ public function __toString(): string
+ {
+ return 'reflection.'.$this->className;
+ }
+
+ /**
+ * @internal
+ */
+ public function __sleep(): array
+ {
+ if (null === $this->hash) {
+ $this->hash = $this->computeHash();
+ $this->loadFiles($this->classReflector);
+ }
+
+ return ['files', 'className', 'hash'];
+ }
+
+ private function loadFiles(\ReflectionClass $class)
+ {
+ foreach ($class->getInterfaces() as $v) {
+ $this->loadFiles($v);
+ }
+ do {
+ $file = $class->getFileName();
+ if (false !== $file && is_file($file)) {
+ foreach ($this->excludedVendors as $vendor) {
+ if (str_starts_with($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
+ $file = false;
+ break;
+ }
+ }
+ if ($file) {
+ $this->files[$file] = null;
+ }
+ }
+ foreach ($class->getTraits() as $v) {
+ $this->loadFiles($v);
+ }
+ } while ($class = $class->getParentClass());
+ }
+
+ private function computeHash(): string
+ {
+ if (null === $this->classReflector) {
+ try {
+ $this->classReflector = new \ReflectionClass($this->className);
+ } catch (\ReflectionException $e) {
+ // the class does not exist anymore
+ return false;
+ }
+ }
+ $hash = hash_init('md5');
+
+ foreach ($this->generateSignature($this->classReflector) as $info) {
+ hash_update($hash, $info);
+ }
+
+ return hash_final($hash);
+ }
+
+ 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();
+
+ if ($class->isTrait()) {
+ yield print_r(class_uses($class->name), true);
+ } else {
+ yield print_r(class_parents($class->name), true);
+ yield print_r(class_implements($class->name), true);
+ yield print_r($class->getConstants(), true);
+ }
+
+ if (!$class->isInterface()) {
+ $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() ? '' : '';
+ yield $p->isPublic() ? 'public' : 'protected';
+ yield $p->isStatic() ? 'static' : '';
+ yield '$'.$p->name;
+ yield print_r(isset($defaults[$p->name]) && !\is_object($defaults[$p->name]) ? $defaults[$p->name] : null, true);
+ }
+ }
+
+ $defined = \Closure::bind(static function ($c) { return \defined($c); }, null, $class->name);
+
+ foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) {
+ if (\PHP_VERSION_ID >= 80000) {
+ foreach ($m->getAttributes() as $a) {
+ $attributes[] = [$a->getName(), \PHP_VERSION_ID >= 80100 ? (string) $a : $a->getArguments()];
+ }
+ yield print_r($attributes, true);
+ $attributes = [];
+ }
+
+ $defaults = [];
+ $parametersWithUndefinedConstants = [];
+ foreach ($m->getParameters() 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 = [];
+ }
+
+ if (!$p->isDefaultValueAvailable()) {
+ $defaults[$p->name] = null;
+
+ continue;
+ }
+
+ if (\PHP_VERSION_ID >= 80100) {
+ $defaults[$p->name] = (string) $p;
+
+ continue;
+ }
+
+ if (!$p->isDefaultValueConstant() || $defined($p->getDefaultValueConstantName())) {
+ $defaults[$p->name] = $p->getDefaultValue();
+
+ continue;
+ }
+
+ $defaults[$p->name] = $p->getDefaultValueConstantName();
+ $parametersWithUndefinedConstants[$p->name] = true;
+ }
+
+ if (!$parametersWithUndefinedConstants) {
+ yield preg_replace('/^ @@.*/m', '', $m);
+ } else {
+ $t = $m->getReturnType();
+ $stack = [
+ $m->getDocComment(),
+ $m->getName(),
+ $m->isAbstract(),
+ $m->isFinal(),
+ $m->isStatic(),
+ $m->isPublic(),
+ $m->isPrivate(),
+ $m->isProtected(),
+ $m->returnsReference(),
+ $t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t,
+ ];
+
+ foreach ($m->getParameters() as $p) {
+ if (!isset($parametersWithUndefinedConstants[$p->name])) {
+ $stack[] = (string) $p;
+ } else {
+ $t = $p->getType();
+ $stack[] = $p->isOptional();
+ $stack[] = $t instanceof \ReflectionNamedType ? ((string) $t->allowsNull()).$t->getName() : (string) $t;
+ $stack[] = $p->isPassedByReference();
+ $stack[] = $p->isVariadic();
+ $stack[] = $p->getName();
+ }
+ }
+
+ yield implode(',', $stack);
+ }
+
+ yield print_r($defaults, true);
+ }
+
+ if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) {
+ return;
+ }
+
+ if (interface_exists(EventSubscriberInterface::class, false) && $class->isSubclassOf(EventSubscriberInterface::class)) {
+ yield EventSubscriberInterface::class;
+ yield print_r($class->name::getSubscribedEvents(), true);
+ }
+
+ if (interface_exists(MessageSubscriberInterface::class, false) && $class->isSubclassOf(MessageSubscriberInterface::class)) {
+ yield MessageSubscriberInterface::class;
+ foreach ($class->name::getHandledMessages() as $key => $value) {
+ yield $key.print_r($value, true);
+ }
+ }
+
+ if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) {
+ yield ServiceSubscriberInterface::class;
+ yield print_r($class->name::getSubscribedServices(), true);
+ }
+ }
+}
diff --git a/vendor/symfony/config/Resource/ResourceInterface.php b/vendor/symfony/config/Resource/ResourceInterface.php
new file mode 100644
index 0000000..9a0cd9a
--- /dev/null
+++ b/vendor/symfony/config/Resource/ResourceInterface.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+/**
+ * ResourceInterface is the interface that must be implemented by all Resource classes.
+ *
+ * @author Fabien Potencier
+ */
+interface ResourceInterface
+{
+ /**
+ * Returns a string representation of the Resource.
+ *
+ * This method is necessary to allow for resource de-duplication, for example by means
+ * of array_unique(). The string returned need not have a particular meaning, but has
+ * to be identical for different ResourceInterface instances referring to the same
+ * resource; and it should be unlikely to collide with that of other, unrelated
+ * resource instances.
+ */
+ public function __toString();
+}
diff --git a/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php
new file mode 100644
index 0000000..e1727b9
--- /dev/null
+++ b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Resource;
+
+use Symfony\Component\Config\ResourceCheckerInterface;
+
+/**
+ * Resource checker for instances of SelfCheckingResourceInterface.
+ *
+ * As these resources perform the actual check themselves, we can provide
+ * this class as a standard way of validating them.
+ *
+ * @author Matthias Pigulla
+ */
+class SelfCheckingResourceChecker implements ResourceCheckerInterface
+{
+ // Common shared cache, because this checker can be used in different
+ // situations. For example, when using the full stack framework, the router
+ // and the container have their own cache. But they may check the very same
+ // resources
+ private static $cache = [];
+
+ public function supports(ResourceInterface $metadata)
+ {
+ return $metadata instanceof SelfCheckingResourceInterface;
+ }
+
+ /**
+ * @param SelfCheckingResourceInterface $resource
+ */
+ public function isFresh(ResourceInterface $resource, int $timestamp)
+ {
+ $key = "$resource:$timestamp";
+
+ return self::$cache[$key] ?? self::$cache[$key] = $resource->isFresh($timestamp);
+ }
+}
diff --git a/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php
new file mode 100644
index 0000000..2c1a378
--- /dev/null
+++ b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.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\Config\Resource;
+
+/**
+ * Interface for Resources that can check for freshness autonomously,
+ * without special support from external services.
+ *
+ * @author Matthias Pigulla
+ */
+interface SelfCheckingResourceInterface extends ResourceInterface
+{
+ /**
+ * Returns true if the resource has not been updated since the given timestamp.
+ *
+ * @param int $timestamp The last time the resource was loaded
+ *
+ * @return bool
+ */
+ public function isFresh(int $timestamp);
+}
diff --git a/vendor/symfony/config/ResourceCheckerConfigCache.php b/vendor/symfony/config/ResourceCheckerConfigCache.php
new file mode 100644
index 0000000..a0d301c
--- /dev/null
+++ b/vendor/symfony/config/ResourceCheckerConfigCache.php
@@ -0,0 +1,187 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+use Symfony\Component\Filesystem\Exception\IOException;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface
+ * to check whether cached data is still fresh.
+ *
+ * @author Matthias Pigulla
+ */
+class ResourceCheckerConfigCache implements ConfigCacheInterface
+{
+ /**
+ * @var string
+ */
+ private $file;
+
+ /**
+ * @var iterable
+ */
+ private $resourceCheckers;
+
+ /**
+ * @param string $file The absolute cache path
+ * @param iterable $resourceCheckers The ResourceCheckers to use for the freshness check
+ */
+ public function __construct(string $file, iterable $resourceCheckers = [])
+ {
+ $this->file = $file;
+ $this->resourceCheckers = $resourceCheckers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPath()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Checks if the cache is still fresh.
+ *
+ * This implementation will make a decision solely based on the ResourceCheckers
+ * passed in the constructor.
+ *
+ * The first ResourceChecker that supports a given resource is considered authoritative.
+ * Resources with no matching ResourceChecker will silently be ignored and considered fresh.
+ *
+ * @return bool
+ */
+ public function isFresh()
+ {
+ if (!is_file($this->file)) {
+ return false;
+ }
+
+ if ($this->resourceCheckers instanceof \Traversable && !$this->resourceCheckers instanceof \Countable) {
+ $this->resourceCheckers = iterator_to_array($this->resourceCheckers);
+ }
+
+ if (!\count($this->resourceCheckers)) {
+ return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all
+ }
+
+ $metadata = $this->getMetaFile();
+
+ if (!is_file($metadata)) {
+ return false;
+ }
+
+ $meta = $this->safelyUnserialize($metadata);
+
+ if (false === $meta) {
+ return false;
+ }
+
+ $time = filemtime($this->file);
+
+ foreach ($meta as $resource) {
+ foreach ($this->resourceCheckers as $checker) {
+ if (!$checker->supports($resource)) {
+ continue; // next checker
+ }
+ if ($checker->isFresh($resource, $time)) {
+ break; // no need to further check this resource
+ }
+
+ return false; // cache is stale
+ }
+ // no suitable checker found, ignore this resource
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes cache.
+ *
+ * @param string $content The content to write in the cache
+ * @param ResourceInterface[] $metadata An array of metadata
+ *
+ * @throws \RuntimeException When cache file can't be written
+ */
+ public function write(string $content, array $metadata = null)
+ {
+ $mode = 0666;
+ $umask = umask();
+ $filesystem = new Filesystem();
+ $filesystem->dumpFile($this->file, $content);
+ try {
+ $filesystem->chmod($this->file, $mode, $umask);
+ } catch (IOException $e) {
+ // discard chmod failure (some filesystem may not support it)
+ }
+
+ if (null !== $metadata) {
+ $filesystem->dumpFile($this->getMetaFile(), serialize($metadata));
+ try {
+ $filesystem->chmod($this->getMetaFile(), $mode, $umask);
+ } catch (IOException $e) {
+ // discard chmod failure (some filesystem may not support it)
+ }
+ }
+
+ if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
+ @opcache_invalidate($this->file, true);
+ }
+ }
+
+ /**
+ * Gets the meta file path.
+ */
+ private function getMetaFile(): string
+ {
+ return $this->file.'.meta';
+ }
+
+ private function safelyUnserialize(string $file)
+ {
+ $meta = false;
+ $content = file_get_contents($file);
+ $signalingException = new \UnexpectedValueException();
+ $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback');
+ $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) {
+ if (__FILE__ === $file) {
+ throw $signalingException;
+ }
+
+ return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
+ });
+
+ try {
+ $meta = unserialize($content);
+ } catch (\Throwable $e) {
+ if ($e !== $signalingException) {
+ throw $e;
+ }
+ } finally {
+ restore_error_handler();
+ ini_set('unserialize_callback_func', $prevUnserializeHandler);
+ }
+
+ return $meta;
+ }
+
+ /**
+ * @internal
+ */
+ public static function handleUnserializeCallback(string $class)
+ {
+ trigger_error('Class not found: '.$class);
+ }
+}
diff --git a/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php
new file mode 100644
index 0000000..21b7433
--- /dev/null
+++ b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.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\Config;
+
+/**
+ * A ConfigCacheFactory implementation that validates the
+ * cache with an arbitrary set of ResourceCheckers.
+ *
+ * @author Matthias Pigulla
+ */
+class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface
+{
+ private $resourceCheckers = [];
+
+ /**
+ * @param iterable $resourceCheckers
+ */
+ public function __construct(iterable $resourceCheckers = [])
+ {
+ $this->resourceCheckers = $resourceCheckers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function cache(string $file, callable $callable)
+ {
+ $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers);
+ if (!$cache->isFresh()) {
+ $callable($cache);
+ }
+
+ return $cache;
+ }
+}
diff --git a/vendor/symfony/config/ResourceCheckerInterface.php b/vendor/symfony/config/ResourceCheckerInterface.php
new file mode 100644
index 0000000..6b1c6c5
--- /dev/null
+++ b/vendor/symfony/config/ResourceCheckerInterface.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * Interface for ResourceCheckers.
+ *
+ * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated
+ * metadata resources are passed to ResourceCheckers. The ResourceCheckers
+ * can then inspect the resources and decide whether the cache can be considered
+ * fresh or not.
+ *
+ * @author Matthias Pigulla
+ * @author Benjamin Klotz
+ */
+interface ResourceCheckerInterface
+{
+ /**
+ * Queries the ResourceChecker whether it can validate a given
+ * resource or not.
+ *
+ * @return bool
+ */
+ public function supports(ResourceInterface $metadata);
+
+ /**
+ * Validates the resource.
+ *
+ * @param int $timestamp The timestamp at which the cache associated with this resource was created
+ *
+ * @return bool
+ */
+ public function isFresh(ResourceInterface $resource, int $timestamp);
+}
diff --git a/vendor/symfony/config/Util/Exception/InvalidXmlException.php b/vendor/symfony/config/Util/Exception/InvalidXmlException.php
new file mode 100644
index 0000000..a335bbd
--- /dev/null
+++ b/vendor/symfony/config/Util/Exception/InvalidXmlException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Util\Exception;
+
+/**
+ * Exception class for when XML parsing with an XSD schema file path or a callable validator produces errors unrelated
+ * to the actual XML parsing.
+ *
+ * @author Ole Rößner
+ */
+class InvalidXmlException extends XmlParsingException
+{
+}
diff --git a/vendor/symfony/config/Util/Exception/XmlParsingException.php b/vendor/symfony/config/Util/Exception/XmlParsingException.php
new file mode 100644
index 0000000..9bceed6
--- /dev/null
+++ b/vendor/symfony/config/Util/Exception/XmlParsingException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Util\Exception;
+
+/**
+ * Exception class for when XML cannot be parsed properly.
+ *
+ * @author Ole Rößner
+ */
+class XmlParsingException extends \InvalidArgumentException
+{
+}
diff --git a/vendor/symfony/config/Util/XmlUtils.php b/vendor/symfony/config/Util/XmlUtils.php
new file mode 100644
index 0000000..8258a06
--- /dev/null
+++ b/vendor/symfony/config/Util/XmlUtils.php
@@ -0,0 +1,289 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Config\Util;
+
+use Symfony\Component\Config\Util\Exception\InvalidXmlException;
+use Symfony\Component\Config\Util\Exception\XmlParsingException;
+
+/**
+ * XMLUtils is a bunch of utility methods to XML operations.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Fabien Potencier
+ * @author Martin Hasoň
+ * @author Ole Rößner
+ */
+class XmlUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Parses an XML string.
+ *
+ * @param string $content An XML string
+ * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
+ *
+ * @return \DOMDocument
+ *
+ * @throws XmlParsingException When parsing of XML file returns error
+ * @throws InvalidXmlException When parsing of XML with schema or callable produces any errors unrelated to the XML parsing itself
+ * @throws \RuntimeException When DOM extension is missing
+ */
+ public static function parse(string $content, $schemaOrCallable = null)
+ {
+ if (!\extension_loaded('dom')) {
+ throw new \LogicException('Extension DOM is required.');
+ }
+
+ $internalErrors = libxml_use_internal_errors(true);
+ if (\LIBXML_VERSION < 20900) {
+ $disableEntities = libxml_disable_entity_loader(true);
+ }
+ libxml_clear_errors();
+
+ $dom = new \DOMDocument();
+ $dom->validateOnParse = true;
+ if (!$dom->loadXML($content, \LIBXML_NONET | (\defined('LIBXML_COMPACT') ? \LIBXML_COMPACT : 0))) {
+ if (\LIBXML_VERSION < 20900) {
+ libxml_disable_entity_loader($disableEntities);
+ }
+
+ throw new XmlParsingException(implode("\n", static::getXmlErrors($internalErrors)));
+ }
+
+ $dom->normalizeDocument();
+
+ libxml_use_internal_errors($internalErrors);
+ if (\LIBXML_VERSION < 20900) {
+ libxml_disable_entity_loader($disableEntities);
+ }
+
+ foreach ($dom->childNodes as $child) {
+ if (\XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
+ throw new XmlParsingException('Document types are not allowed.');
+ }
+ }
+
+ if (null !== $schemaOrCallable) {
+ $internalErrors = libxml_use_internal_errors(true);
+ libxml_clear_errors();
+
+ $e = null;
+ if (\is_callable($schemaOrCallable)) {
+ try {
+ $valid = $schemaOrCallable($dom, $internalErrors);
+ } catch (\Exception $e) {
+ $valid = false;
+ }
+ } elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) {
+ $schemaSource = file_get_contents((string) $schemaOrCallable);
+ $valid = @$dom->schemaValidateSource($schemaSource);
+ } else {
+ libxml_use_internal_errors($internalErrors);
+
+ throw new XmlParsingException('The schemaOrCallable argument has to be a valid path to XSD file or callable.');
+ }
+
+ if (!$valid) {
+ $messages = static::getXmlErrors($internalErrors);
+ if (empty($messages)) {
+ throw new InvalidXmlException('The XML is not valid.', 0, $e);
+ }
+ throw new XmlParsingException(implode("\n", $messages), 0, $e);
+ }
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $dom;
+ }
+
+ /**
+ * Loads an XML file.
+ *
+ * @param string $file An XML file path
+ * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation
+ *
+ * @return \DOMDocument
+ *
+ * @throws \InvalidArgumentException When loading of XML file returns error
+ * @throws XmlParsingException When XML parsing returns any errors
+ * @throws \RuntimeException When DOM extension is missing
+ */
+ public static function loadFile(string $file, $schemaOrCallable = null)
+ {
+ if (!is_file($file)) {
+ throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.', $file));
+ }
+
+ if (!is_readable($file)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $file));
+ }
+
+ $content = @file_get_contents($file);
+
+ if ('' === trim($content)) {
+ throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid XML, it is empty.', $file));
+ }
+
+ try {
+ return static::parse($content, $schemaOrCallable);
+ } catch (InvalidXmlException $e) {
+ throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious());
+ }
+ }
+
+ /**
+ * Converts a \DOMElement object to a PHP array.
+ *
+ * The following rules applies during the conversion:
+ *
+ * * Each tag is converted to a key value or an array
+ * if there is more than one "value"
+ *
+ * * The content of a tag is set under a "value" key (bar)
+ * if the tag also has some nested tags
+ *
+ * * The attributes are converted to keys ()
+ *
+ * * The nested-tags are converted to keys (bar)
+ *
+ * @param \DOMElement $element A \DOMElement instance
+ * @param bool $checkPrefix Check prefix in an element or an attribute name
+ *
+ * @return mixed
+ */
+ public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true)
+ {
+ $prefix = (string) $element->prefix;
+ $empty = true;
+ $config = [];
+ foreach ($element->attributes as $name => $node) {
+ if ($checkPrefix && !\in_array((string) $node->prefix, ['', $prefix], true)) {
+ continue;
+ }
+ $config[$name] = static::phpize($node->value);
+ $empty = false;
+ }
+
+ $nodeValue = false;
+ foreach ($element->childNodes as $node) {
+ if ($node instanceof \DOMText) {
+ if ('' !== trim($node->nodeValue)) {
+ $nodeValue = trim($node->nodeValue);
+ $empty = false;
+ }
+ } elseif ($checkPrefix && $prefix != (string) $node->prefix) {
+ continue;
+ } elseif (!$node instanceof \DOMComment) {
+ $value = static::convertDomElementToArray($node, $checkPrefix);
+
+ $key = $node->localName;
+ if (isset($config[$key])) {
+ if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) {
+ $config[$key] = [$config[$key]];
+ }
+ $config[$key][] = $value;
+ } else {
+ $config[$key] = $value;
+ }
+
+ $empty = false;
+ }
+ }
+
+ if (false !== $nodeValue) {
+ $value = static::phpize($nodeValue);
+ if (\count($config)) {
+ $config['value'] = $value;
+ } else {
+ $config = $value;
+ }
+ }
+
+ return !$empty ? $config : null;
+ }
+
+ /**
+ * Converts an xml value to a PHP type.
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ public static function phpize($value)
+ {
+ $value = (string) $value;
+ $lowercaseValue = strtolower($value);
+
+ switch (true) {
+ case 'null' === $lowercaseValue:
+ return null;
+ case ctype_digit($value):
+ case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)):
+ $raw = $value;
+ $cast = (int) $value;
+
+ return self::isOctal($value) ? \intval($value, 8) : (($raw === (string) $cast) ? $cast : $raw);
+ case 'true' === $lowercaseValue:
+ return true;
+ case 'false' === $lowercaseValue:
+ return false;
+ case isset($value[1]) && '0b' == $value[0].$value[1] && preg_match('/^0b[01]*$/', $value):
+ return bindec($value);
+ case is_numeric($value):
+ return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value;
+ case preg_match('/^0x[0-9a-f]++$/i', $value):
+ return hexdec($value);
+ case preg_match('/^[+-]?[0-9]+(\.[0-9]+)?$/', $value):
+ return (float) $value;
+ default:
+ return $value;
+ }
+ }
+
+ protected static function getXmlErrors(bool $internalErrors)
+ {
+ $errors = [];
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
+ \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ $error->code,
+ trim($error->message),
+ $error->file ?: 'n/a',
+ $error->line,
+ $error->column
+ );
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $errors;
+ }
+
+ private static function isOctal(string $str): bool
+ {
+ if ('-' === $str[0]) {
+ $str = substr($str, 1);
+ }
+
+ return $str === '0'.decoct(\intval($str, 8));
+ }
+}
diff --git a/vendor/symfony/config/composer.json b/vendor/symfony/config/composer.json
new file mode 100644
index 0000000..b357ed3
--- /dev/null
+++ b/vendor/symfony/config/composer.json
@@ -0,0 +1,46 @@
+{
+ "name": "symfony/config",
+ "type": "library",
+ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/filesystem": "^4.4|^5.0|^6.0",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/polyfill-php81": "^1.22"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+ "symfony/finder": "^4.4|^5.0|^6.0",
+ "symfony/messenger": "^4.4|^5.0|^6.0",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/yaml": "^4.4|^5.0|^6.0"
+ },
+ "conflict": {
+ "symfony/finder": "<4.4"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Config\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php
new file mode 100644
index 0000000..3decfc0
--- /dev/null
+++ b/vendor/symfony/console/Application.php
@@ -0,0 +1,1276 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\CompleteCommand;
+use Symfony\Component\Console\Command\DumpCompletionCommand;
+use Symfony\Component\Console\Command\HelpCommand;
+use Symfony\Component\Console\Command\LazyCommand;
+use Symfony\Component\Console\Command\ListCommand;
+use Symfony\Component\Console\Command\SignalableCommandInterface;
+use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Event\ConsoleCommandEvent;
+use Symfony\Component\Console\Event\ConsoleErrorEvent;
+use Symfony\Component\Console\Event\ConsoleSignalEvent;
+use Symfony\Component\Console\Event\ConsoleTerminateEvent;
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+use Symfony\Component\Console\Exception\ExceptionInterface;
+use Symfony\Component\Console\Exception\LogicException;
+use Symfony\Component\Console\Exception\NamespaceNotFoundException;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Helper\DebugFormatterHelper;
+use Symfony\Component\Console\Helper\FormatterHelper;
+use Symfony\Component\Console\Helper\Helper;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Helper\ProcessHelper;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\ArgvInput;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputAwareInterface;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\SignalRegistry\SignalRegistry;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\ErrorHandler\ErrorHandler;
+use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
+use Symfony\Contracts\Service\ResetInterface;
+
+/**
+ * An Application is the container for a collection of commands.
+ *
+ * It is the main entry point of a Console application.
+ *
+ * This class is optimized for a standard CLI environment.
+ *
+ * Usage:
+ *
+ * $app = new Application('myapp', '1.0 (stable)');
+ * $app->add(new SimpleCommand());
+ * $app->run();
+ *
+ * @author Fabien Potencier
+ */
+class Application implements ResetInterface
+{
+ private $commands = [];
+ private $wantHelps = false;
+ private $runningCommand;
+ private $name;
+ private $version;
+ private $commandLoader;
+ private $catchExceptions = true;
+ private $autoExit = true;
+ private $definition;
+ private $helperSet;
+ private $dispatcher;
+ private $terminal;
+ private $defaultCommand;
+ private $singleCommand = false;
+ private $initialized;
+ private $signalRegistry;
+ private $signalsToDispatchEvent = [];
+
+ public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
+ {
+ $this->name = $name;
+ $this->version = $version;
+ $this->terminal = new Terminal();
+ $this->defaultCommand = 'list';
+ if (\defined('SIGINT') && SignalRegistry::isSupported()) {
+ $this->signalRegistry = new SignalRegistry();
+ $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
+ }
+ }
+
+ /**
+ * @final
+ */
+ public function setDispatcher(EventDispatcherInterface $dispatcher)
+ {
+ $this->dispatcher = $dispatcher;
+ }
+
+ public function setCommandLoader(CommandLoaderInterface $commandLoader)
+ {
+ $this->commandLoader = $commandLoader;
+ }
+
+ public function getSignalRegistry(): SignalRegistry
+ {
+ if (!$this->signalRegistry) {
+ throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
+ }
+
+ return $this->signalRegistry;
+ }
+
+ public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
+ {
+ $this->signalsToDispatchEvent = $signalsToDispatchEvent;
+ }
+
+ /**
+ * Runs the current application.
+ *
+ * @return int 0 if everything went fine, or an error code
+ *
+ * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
+ */
+ public function run(InputInterface $input = null, OutputInterface $output = null)
+ {
+ if (\function_exists('putenv')) {
+ @putenv('LINES='.$this->terminal->getHeight());
+ @putenv('COLUMNS='.$this->terminal->getWidth());
+ }
+
+ if (null === $input) {
+ $input = new ArgvInput();
+ }
+
+ if (null === $output) {
+ $output = new ConsoleOutput();
+ }
+
+ $renderException = function (\Throwable $e) use ($output) {
+ if ($output instanceof ConsoleOutputInterface) {
+ $this->renderThrowable($e, $output->getErrorOutput());
+ } else {
+ $this->renderThrowable($e, $output);
+ }
+ };
+ if ($phpHandler = set_exception_handler($renderException)) {
+ restore_exception_handler();
+ if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
+ $errorHandler = true;
+ } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
+ $phpHandler[0]->setExceptionHandler($errorHandler);
+ }
+ }
+
+ $this->configureIO($input, $output);
+
+ try {
+ $exitCode = $this->doRun($input, $output);
+ } catch (\Exception $e) {
+ if (!$this->catchExceptions) {
+ throw $e;
+ }
+
+ $renderException($e);
+
+ $exitCode = $e->getCode();
+ if (is_numeric($exitCode)) {
+ $exitCode = (int) $exitCode;
+ if ($exitCode <= 0) {
+ $exitCode = 1;
+ }
+ } else {
+ $exitCode = 1;
+ }
+ } finally {
+ // if the exception handler changed, keep it
+ // otherwise, unregister $renderException
+ if (!$phpHandler) {
+ if (set_exception_handler($renderException) === $renderException) {
+ restore_exception_handler();
+ }
+ restore_exception_handler();
+ } elseif (!$errorHandler) {
+ $finalHandler = $phpHandler[0]->setExceptionHandler(null);
+ if ($finalHandler !== $renderException) {
+ $phpHandler[0]->setExceptionHandler($finalHandler);
+ }
+ }
+ }
+
+ if ($this->autoExit) {
+ if ($exitCode > 255) {
+ $exitCode = 255;
+ }
+
+ exit($exitCode);
+ }
+
+ return $exitCode;
+ }
+
+ /**
+ * Runs the current application.
+ *
+ * @return int 0 if everything went fine, or an error code
+ */
+ public function doRun(InputInterface $input, OutputInterface $output)
+ {
+ if (true === $input->hasParameterOption(['--version', '-V'], true)) {
+ $output->writeln($this->getLongVersion());
+
+ return 0;
+ }
+
+ try {
+ // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
+ $input->bind($this->getDefinition());
+ } catch (ExceptionInterface $e) {
+ // Errors must be ignored, full binding/validation happens later when the command is known.
+ }
+
+ $name = $this->getCommandName($input);
+ if (true === $input->hasParameterOption(['--help', '-h'], true)) {
+ if (!$name) {
+ $name = 'help';
+ $input = new ArrayInput(['command_name' => $this->defaultCommand]);
+ } else {
+ $this->wantHelps = true;
+ }
+ }
+
+ if (!$name) {
+ $name = $this->defaultCommand;
+ $definition = $this->getDefinition();
+ $definition->setArguments(array_merge(
+ $definition->getArguments(),
+ [
+ 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
+ ]
+ ));
+ }
+
+ try {
+ $this->runningCommand = null;
+ // the command name MUST be the first element of the input
+ $command = $this->find($name);
+ } catch (\Throwable $e) {
+ if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
+ if (null !== $this->dispatcher) {
+ $event = new ConsoleErrorEvent($input, $output, $e);
+ $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
+
+ if (0 === $event->getExitCode()) {
+ return 0;
+ }
+
+ $e = $event->getError();
+ }
+
+ throw $e;
+ }
+
+ $alternative = $alternatives[0];
+
+ $style = new SymfonyStyle($input, $output);
+ $style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
+ if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
+ if (null !== $this->dispatcher) {
+ $event = new ConsoleErrorEvent($input, $output, $e);
+ $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
+
+ return $event->getExitCode();
+ }
+
+ return 1;
+ }
+
+ $command = $this->find($alternative);
+ }
+
+ if ($command instanceof LazyCommand) {
+ $command = $command->getCommand();
+ }
+
+ $this->runningCommand = $command;
+ $exitCode = $this->doRunCommand($command, $input, $output);
+ $this->runningCommand = null;
+
+ return $exitCode;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reset()
+ {
+ }
+
+ public function setHelperSet(HelperSet $helperSet)
+ {
+ $this->helperSet = $helperSet;
+ }
+
+ /**
+ * Get the helper set associated with the command.
+ *
+ * @return HelperSet
+ */
+ public function getHelperSet()
+ {
+ if (!$this->helperSet) {
+ $this->helperSet = $this->getDefaultHelperSet();
+ }
+
+ return $this->helperSet;
+ }
+
+ public function setDefinition(InputDefinition $definition)
+ {
+ $this->definition = $definition;
+ }
+
+ /**
+ * Gets the InputDefinition related to this Application.
+ *
+ * @return InputDefinition
+ */
+ public function getDefinition()
+ {
+ if (!$this->definition) {
+ $this->definition = $this->getDefaultInputDefinition();
+ }
+
+ if ($this->singleCommand) {
+ $inputDefinition = $this->definition;
+ $inputDefinition->setArguments();
+
+ return $inputDefinition;
+ }
+
+ return $this->definition;
+ }
+
+ /**
+ * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
+ */
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if (
+ CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
+ && 'command' === $input->getCompletionName()
+ ) {
+ $suggestions->suggestValues(array_filter(array_map(function (Command $command) {
+ return $command->isHidden() ? null : $command->getName();
+ }, $this->all())));
+
+ return;
+ }
+
+ if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
+ $suggestions->suggestOptions($this->getDefinition()->getOptions());
+
+ return;
+ }
+ }
+
+ /**
+ * Gets the help message.
+ *
+ * @return string
+ */
+ public function getHelp()
+ {
+ return $this->getLongVersion();
+ }
+
+ /**
+ * Gets whether to catch exceptions or not during commands execution.
+ *
+ * @return bool
+ */
+ public function areExceptionsCaught()
+ {
+ return $this->catchExceptions;
+ }
+
+ /**
+ * Sets whether to catch exceptions or not during commands execution.
+ */
+ public function setCatchExceptions(bool $boolean)
+ {
+ $this->catchExceptions = $boolean;
+ }
+
+ /**
+ * Gets whether to automatically exit after a command execution or not.
+ *
+ * @return bool
+ */
+ public function isAutoExitEnabled()
+ {
+ return $this->autoExit;
+ }
+
+ /**
+ * Sets whether to automatically exit after a command execution or not.
+ */
+ public function setAutoExit(bool $boolean)
+ {
+ $this->autoExit = $boolean;
+ }
+
+ /**
+ * Gets the name of the application.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets the application name.
+ **/
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Gets the application version.
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Sets the application version.
+ */
+ public function setVersion(string $version)
+ {
+ $this->version = $version;
+ }
+
+ /**
+ * Returns the long version of the application.
+ *
+ * @return string
+ */
+ public function getLongVersion()
+ {
+ if ('UNKNOWN' !== $this->getName()) {
+ if ('UNKNOWN' !== $this->getVersion()) {
+ return sprintf('%s %s', $this->getName(), $this->getVersion());
+ }
+
+ return $this->getName();
+ }
+
+ return 'Console Tool';
+ }
+
+ /**
+ * Registers a new command.
+ *
+ * @return Command
+ */
+ public function register(string $name)
+ {
+ return $this->add(new Command($name));
+ }
+
+ /**
+ * Adds an array of command objects.
+ *
+ * If a Command is not enabled it will not be added.
+ *
+ * @param Command[] $commands An array of commands
+ */
+ public function addCommands(array $commands)
+ {
+ foreach ($commands as $command) {
+ $this->add($command);
+ }
+ }
+
+ /**
+ * Adds a command object.
+ *
+ * If a command with the same name already exists, it will be overridden.
+ * If the command is not enabled it will not be added.
+ *
+ * @return Command|null
+ */
+ public function add(Command $command)
+ {
+ $this->init();
+
+ $command->setApplication($this);
+
+ if (!$command->isEnabled()) {
+ $command->setApplication(null);
+
+ return null;
+ }
+
+ if (!$command instanceof LazyCommand) {
+ // Will throw if the command is not correctly initialized.
+ $command->getDefinition();
+ }
+
+ if (!$command->getName()) {
+ throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
+ }
+
+ $this->commands[$command->getName()] = $command;
+
+ foreach ($command->getAliases() as $alias) {
+ $this->commands[$alias] = $command;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Returns a registered command by name or alias.
+ *
+ * @return Command
+ *
+ * @throws CommandNotFoundException When given command name does not exist
+ */
+ public function get(string $name)
+ {
+ $this->init();
+
+ if (!$this->has($name)) {
+ throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
+ }
+
+ // When the command has a different name than the one used at the command loader level
+ if (!isset($this->commands[$name])) {
+ throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
+ }
+
+ $command = $this->commands[$name];
+
+ if ($this->wantHelps) {
+ $this->wantHelps = false;
+
+ $helpCommand = $this->get('help');
+ $helpCommand->setCommand($command);
+
+ return $helpCommand;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Returns true if the command exists, false otherwise.
+ *
+ * @return bool
+ */
+ public function has(string $name)
+ {
+ $this->init();
+
+ return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
+ }
+
+ /**
+ * Returns an array of all unique namespaces used by currently registered commands.
+ *
+ * It does not return the global namespace which always exists.
+ *
+ * @return string[]
+ */
+ public function getNamespaces()
+ {
+ $namespaces = [];
+ foreach ($this->all() as $command) {
+ if ($command->isHidden()) {
+ continue;
+ }
+
+ $namespaces[] = $this->extractAllNamespaces($command->getName());
+
+ foreach ($command->getAliases() as $alias) {
+ $namespaces[] = $this->extractAllNamespaces($alias);
+ }
+ }
+
+ return array_values(array_unique(array_filter(array_merge([], ...$namespaces))));
+ }
+
+ /**
+ * Finds a registered namespace by a name or an abbreviation.
+ *
+ * @return string
+ *
+ * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
+ */
+ public function findNamespace(string $namespace)
+ {
+ $allNamespaces = $this->getNamespaces();
+ $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';
+ $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
+
+ if (empty($namespaces)) {
+ $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
+
+ if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
+ if (1 == \count($alternatives)) {
+ $message .= "\n\nDid you mean this?\n ";
+ } else {
+ $message .= "\n\nDid you mean one of these?\n ";
+ }
+
+ $message .= implode("\n ", $alternatives);
+ }
+
+ throw new NamespaceNotFoundException($message, $alternatives);
+ }
+
+ $exact = \in_array($namespace, $namespaces, true);
+ if (\count($namespaces) > 1 && !$exact) {
+ throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
+ }
+
+ return $exact ? $namespace : reset($namespaces);
+ }
+
+ /**
+ * Finds a command by name or alias.
+ *
+ * Contrary to get, this command tries to find the best
+ * match if you give it an abbreviation of a name or alias.
+ *
+ * @return Command
+ *
+ * @throws CommandNotFoundException When command name is incorrect or ambiguous
+ */
+ public function find(string $name)
+ {
+ $this->init();
+
+ $aliases = [];
+
+ foreach ($this->commands as $command) {
+ foreach ($command->getAliases() as $alias) {
+ if (!$this->has($alias)) {
+ $this->commands[$alias] = $command;
+ }
+ }
+ }
+
+ if ($this->has($name)) {
+ return $this->get($name);
+ }
+
+ $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
+ $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*';
+ $commands = preg_grep('{^'.$expr.'}', $allCommands);
+
+ if (empty($commands)) {
+ $commands = preg_grep('{^'.$expr.'}i', $allCommands);
+ }
+
+ // if no commands matched or we just matched namespaces
+ if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
+ if (false !== $pos = strrpos($name, ':')) {
+ // check if a namespace exists and contains commands
+ $this->findNamespace(substr($name, 0, $pos));
+ }
+
+ $message = sprintf('Command "%s" is not defined.', $name);
+
+ if ($alternatives = $this->findAlternatives($name, $allCommands)) {
+ // remove hidden commands
+ $alternatives = array_filter($alternatives, function ($name) {
+ return !$this->get($name)->isHidden();
+ });
+
+ if (1 == \count($alternatives)) {
+ $message .= "\n\nDid you mean this?\n ";
+ } else {
+ $message .= "\n\nDid you mean one of these?\n ";
+ }
+ $message .= implode("\n ", $alternatives);
+ }
+
+ throw new CommandNotFoundException($message, array_values($alternatives));
+ }
+
+ // filter out aliases for commands which are already on the list
+ if (\count($commands) > 1) {
+ $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
+ $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
+ if (!$commandList[$nameOrAlias] instanceof Command) {
+ $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
+ }
+
+ $commandName = $commandList[$nameOrAlias]->getName();
+
+ $aliases[$nameOrAlias] = $commandName;
+
+ return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
+ }));
+ }
+
+ if (\count($commands) > 1) {
+ $usableWidth = $this->terminal->getWidth() - 10;
+ $abbrevs = array_values($commands);
+ $maxLen = 0;
+ foreach ($abbrevs as $abbrev) {
+ $maxLen = max(Helper::width($abbrev), $maxLen);
+ }
+ $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
+ if ($commandList[$cmd]->isHidden()) {
+ unset($commands[array_search($cmd, $commands)]);
+
+ return false;
+ }
+
+ $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
+
+ return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
+ }, array_values($commands));
+
+ if (\count($commands) > 1) {
+ $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
+
+ throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
+ }
+ }
+
+ $command = $this->get(reset($commands));
+
+ if ($command->isHidden()) {
+ throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
+ }
+
+ return $command;
+ }
+
+ /**
+ * Gets the commands (registered in the given namespace if provided).
+ *
+ * The array keys are the full names and the values the command instances.
+ *
+ * @return Command[]
+ */
+ public function all(string $namespace = null)
+ {
+ $this->init();
+
+ if (null === $namespace) {
+ if (!$this->commandLoader) {
+ return $this->commands;
+ }
+
+ $commands = $this->commands;
+ foreach ($this->commandLoader->getNames() as $name) {
+ if (!isset($commands[$name]) && $this->has($name)) {
+ $commands[$name] = $this->get($name);
+ }
+ }
+
+ return $commands;
+ }
+
+ $commands = [];
+ foreach ($this->commands as $name => $command) {
+ if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
+ $commands[$name] = $command;
+ }
+ }
+
+ if ($this->commandLoader) {
+ foreach ($this->commandLoader->getNames() as $name) {
+ if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
+ $commands[$name] = $this->get($name);
+ }
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * Returns an array of possible abbreviations given a set of names.
+ *
+ * @return string[][]
+ */
+ public static function getAbbreviations(array $names)
+ {
+ $abbrevs = [];
+ foreach ($names as $name) {
+ for ($len = \strlen($name); $len > 0; --$len) {
+ $abbrev = substr($name, 0, $len);
+ $abbrevs[$abbrev][] = $name;
+ }
+ }
+
+ return $abbrevs;
+ }
+
+ public function renderThrowable(\Throwable $e, OutputInterface $output): void
+ {
+ $output->writeln('', OutputInterface::VERBOSITY_QUIET);
+
+ $this->doRenderThrowable($e, $output);
+
+ if (null !== $this->runningCommand) {
+ $output->writeln(sprintf('%s', OutputFormatter::escape(sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
+ $output->writeln('', OutputInterface::VERBOSITY_QUIET);
+ }
+ }
+
+ protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
+ {
+ do {
+ $message = trim($e->getMessage());
+ if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
+ $class = get_debug_type($e);
+ $title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
+ $len = Helper::width($title);
+ } else {
+ $len = 0;
+ }
+
+ if (str_contains($message, "@anonymous\0")) {
+ $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
+ return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
+ }, $message);
+ }
+
+ $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
+ $lines = [];
+ foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
+ foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
+ // pre-format lines to get the right string length
+ $lineLength = Helper::width($line) + 4;
+ $lines[] = [$line, $lineLength];
+
+ $len = max($lineLength, $len);
+ }
+ }
+
+ $messages = [];
+ if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
+ $messages[] = sprintf('%s', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
+ }
+ $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len));
+ if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
+ $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::width($title))));
+ }
+ foreach ($lines as $line) {
+ $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
+ }
+ $messages[] = $emptyLine;
+ $messages[] = '';
+
+ $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
+
+ if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
+ $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET);
+
+ // exception related properties
+ $trace = $e->getTrace();
+
+ array_unshift($trace, [
+ 'function' => '',
+ 'file' => $e->getFile() ?: 'n/a',
+ 'line' => $e->getLine() ?: 'n/a',
+ 'args' => [],
+ ]);
+
+ for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
+ $class = $trace[$i]['class'] ?? '';
+ $type = $trace[$i]['type'] ?? '';
+ $function = $trace[$i]['function'] ?? '';
+ $file = $trace[$i]['file'] ?? 'n/a';
+ $line = $trace[$i]['line'] ?? 'n/a';
+
+ $output->writeln(sprintf(' %s%s at %s:%s', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
+ }
+
+ $output->writeln('', OutputInterface::VERBOSITY_QUIET);
+ }
+ } while ($e = $e->getPrevious());
+ }
+
+ /**
+ * Configures the input and output instances based on the user arguments and options.
+ */
+ protected function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ if (true === $input->hasParameterOption(['--ansi'], true)) {
+ $output->setDecorated(true);
+ } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
+ $output->setDecorated(false);
+ }
+
+ if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
+ $input->setInteractive(false);
+ }
+
+ switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
+ case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
+ case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
+ case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
+ case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
+ default: $shellVerbosity = 0; break;
+ }
+
+ if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
+ $shellVerbosity = -1;
+ } else {
+ if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
+ $shellVerbosity = 3;
+ } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
+ $shellVerbosity = 2;
+ } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
+ $shellVerbosity = 1;
+ }
+ }
+
+ if (-1 === $shellVerbosity) {
+ $input->setInteractive(false);
+ }
+
+ if (\function_exists('putenv')) {
+ @putenv('SHELL_VERBOSITY='.$shellVerbosity);
+ }
+ $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
+ $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
+ }
+
+ /**
+ * Runs the current command.
+ *
+ * If an event dispatcher has been attached to the application,
+ * events are also dispatched during the life-cycle of the command.
+ *
+ * @return int 0 if everything went fine, or an error code
+ */
+ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
+ {
+ foreach ($command->getHelperSet() as $helper) {
+ if ($helper instanceof InputAwareInterface) {
+ $helper->setInput($input);
+ }
+ }
+
+ if ($command instanceof SignalableCommandInterface && ($this->signalsToDispatchEvent || $command->getSubscribedSignals())) {
+ if (!$this->signalRegistry) {
+ throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
+ }
+
+ if (Terminal::hasSttyAvailable()) {
+ $sttyMode = shell_exec('stty -g');
+
+ foreach ([\SIGINT, \SIGTERM] as $signal) {
+ $this->signalRegistry->register($signal, static function () use ($sttyMode) {
+ shell_exec('stty '.$sttyMode);
+ });
+ }
+ }
+
+ if ($this->dispatcher) {
+ foreach ($this->signalsToDispatchEvent as $signal) {
+ $event = new ConsoleSignalEvent($command, $input, $output, $signal);
+
+ $this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
+ $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
+
+ // No more handlers, we try to simulate PHP default behavior
+ if (!$hasNext) {
+ if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
+ exit(0);
+ }
+ }
+ });
+ }
+ }
+
+ foreach ($command->getSubscribedSignals() as $signal) {
+ $this->signalRegistry->register($signal, [$command, 'handleSignal']);
+ }
+ }
+
+ if (null === $this->dispatcher) {
+ return $command->run($input, $output);
+ }
+
+ // bind before the console.command event, so the listeners have access to input options/arguments
+ try {
+ $command->mergeApplicationDefinition();
+ $input->bind($command->getDefinition());
+ } catch (ExceptionInterface $e) {
+ // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
+ }
+
+ $event = new ConsoleCommandEvent($command, $input, $output);
+ $e = null;
+
+ try {
+ $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
+
+ if ($event->commandShouldRun()) {
+ $exitCode = $command->run($input, $output);
+ } else {
+ $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
+ }
+ } catch (\Throwable $e) {
+ $event = new ConsoleErrorEvent($input, $output, $e, $command);
+ $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
+ $e = $event->getError();
+
+ if (0 === $exitCode = $event->getExitCode()) {
+ $e = null;
+ }
+ }
+
+ $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
+ $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
+
+ if (null !== $e) {
+ throw $e;
+ }
+
+ return $event->getExitCode();
+ }
+
+ /**
+ * Gets the name of the command based on input.
+ *
+ * @return string|null
+ */
+ protected function getCommandName(InputInterface $input)
+ {
+ return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
+ }
+
+ /**
+ * Gets the default input definition.
+ *
+ * @return InputDefinition
+ */
+ protected function getDefaultInputDefinition()
+ {
+ return new InputDefinition([
+ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
+ new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the '.$this->defaultCommand.' command'),
+ new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
+ new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
+ new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
+ new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null),
+ new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
+ ]);
+ }
+
+ /**
+ * Gets the default commands that should always be available.
+ *
+ * @return Command[]
+ */
+ protected function getDefaultCommands()
+ {
+ return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
+ }
+
+ /**
+ * Gets the default helper set with the helpers that should always be available.
+ *
+ * @return HelperSet
+ */
+ protected function getDefaultHelperSet()
+ {
+ return new HelperSet([
+ new FormatterHelper(),
+ new DebugFormatterHelper(),
+ new ProcessHelper(),
+ new QuestionHelper(),
+ ]);
+ }
+
+ /**
+ * Returns abbreviated suggestions in string format.
+ */
+ private function getAbbreviationSuggestions(array $abbrevs): string
+ {
+ return ' '.implode("\n ", $abbrevs);
+ }
+
+ /**
+ * Returns the namespace part of the command name.
+ *
+ * This method is not part of public API and should not be used directly.
+ *
+ * @return string
+ */
+ public function extractNamespace(string $name, int $limit = null)
+ {
+ $parts = explode(':', $name, -1);
+
+ return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
+ }
+
+ /**
+ * Finds alternative of $name among $collection,
+ * if nothing is found in $collection, try in $abbrevs.
+ *
+ * @return string[]
+ */
+ private function findAlternatives(string $name, iterable $collection): array
+ {
+ $threshold = 1e3;
+ $alternatives = [];
+
+ $collectionParts = [];
+ foreach ($collection as $item) {
+ $collectionParts[$item] = explode(':', $item);
+ }
+
+ foreach (explode(':', $name) as $i => $subname) {
+ foreach ($collectionParts as $collectionName => $parts) {
+ $exists = isset($alternatives[$collectionName]);
+ if (!isset($parts[$i]) && $exists) {
+ $alternatives[$collectionName] += $threshold;
+ continue;
+ } elseif (!isset($parts[$i])) {
+ continue;
+ }
+
+ $lev = levenshtein($subname, $parts[$i]);
+ if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
+ $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
+ } elseif ($exists) {
+ $alternatives[$collectionName] += $threshold;
+ }
+ }
+ }
+
+ foreach ($collection as $item) {
+ $lev = levenshtein($name, $item);
+ if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) {
+ $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
+ }
+ }
+
+ $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
+ ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
+
+ return array_keys($alternatives);
+ }
+
+ /**
+ * Sets the default Command name.
+ *
+ * @return $this
+ */
+ public function setDefaultCommand(string $commandName, bool $isSingleCommand = false)
+ {
+ $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];
+
+ if ($isSingleCommand) {
+ // Ensure the command exist
+ $this->find($commandName);
+
+ $this->singleCommand = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @internal
+ */
+ public function isSingleCommand(): bool
+ {
+ return $this->singleCommand;
+ }
+
+ private function splitStringByWidth(string $string, int $width): array
+ {
+ // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
+ // additionally, array_slice() is not enough as some character has doubled width.
+ // we need a function to split string not by character count but by string width
+ if (false === $encoding = mb_detect_encoding($string, null, true)) {
+ return str_split($string, $width);
+ }
+
+ $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
+ $lines = [];
+ $line = '';
+
+ $offset = 0;
+ while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
+ $offset += \strlen($m[0]);
+
+ foreach (preg_split('//u', $m[0]) as $char) {
+ // test if $char could be appended to current line
+ if (mb_strwidth($line.$char, 'utf8') <= $width) {
+ $line .= $char;
+ continue;
+ }
+ // if not, push current line to array and make new line
+ $lines[] = str_pad($line, $width);
+ $line = $char;
+ }
+ }
+
+ $lines[] = \count($lines) ? str_pad($line, $width) : $line;
+
+ mb_convert_variables($encoding, 'utf8', $lines);
+
+ return $lines;
+ }
+
+ /**
+ * Returns all namespaces of the command name.
+ *
+ * @return string[]
+ */
+ private function extractAllNamespaces(string $name): array
+ {
+ // -1 as third argument is needed to skip the command short name when exploding
+ $parts = explode(':', $name, -1);
+ $namespaces = [];
+
+ foreach ($parts as $part) {
+ if (\count($namespaces)) {
+ $namespaces[] = end($namespaces).':'.$part;
+ } else {
+ $namespaces[] = $part;
+ }
+ }
+
+ return $namespaces;
+ }
+
+ private function init()
+ {
+ if ($this->initialized) {
+ return;
+ }
+ $this->initialized = true;
+
+ foreach ($this->getDefaultCommands() as $command) {
+ $this->add($command);
+ }
+ }
+}
diff --git a/vendor/symfony/console/Attribute/AsCommand.php b/vendor/symfony/console/Attribute/AsCommand.php
new file mode 100644
index 0000000..b337f54
--- /dev/null
+++ b/vendor/symfony/console/Attribute/AsCommand.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Attribute;
+
+/**
+ * Service tag to autoconfigure commands.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class AsCommand
+{
+ public function __construct(
+ public string $name,
+ public ?string $description = null,
+ array $aliases = [],
+ bool $hidden = false,
+ ) {
+ if (!$hidden && !$aliases) {
+ return;
+ }
+
+ $name = explode('|', $name);
+ $name = array_merge($name, $aliases);
+
+ if ($hidden && '' !== $name[0]) {
+ array_unshift($name, '');
+ }
+
+ $this->name = implode('|', $name);
+ }
+}
diff --git a/vendor/symfony/console/CHANGELOG.md b/vendor/symfony/console/CHANGELOG.md
new file mode 100644
index 0000000..6662dd1
--- /dev/null
+++ b/vendor/symfony/console/CHANGELOG.md
@@ -0,0 +1,217 @@
+CHANGELOG
+=========
+
+5.4
+---
+
+ * Add `TesterTrait::assertCommandIsSuccessful()` to test command
+ * Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement
+
+5.3
+---
+
+ * Add `GithubActionReporter` to render annotations in a Github Action
+ * Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options
+ * Add the `Command::$defaultDescription` static property and the `description` attribute
+ on the `console.command` tag to allow the `list` command to instantiate commands lazily
+ * Add option `--short` to the `list` command
+ * Add support for bright colors
+ * Add `#[AsCommand]` attribute for declaring commands on PHP 8
+ * Add `Helper::width()` and `Helper::length()`
+ * The `--ansi` and `--no-ansi` options now default to `null`.
+
+5.2.0
+-----
+
+ * Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester`
+ * added support for multiline responses to questions through `Question::setMultiline()`
+ and `Question::isMultiline()`
+ * Added `SignalRegistry` class to stack signals handlers
+ * Added support for signals:
+ * Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods
+ * Added `SignalableCommandInterface` interface
+ * Added `TableCellStyle` class to customize table cell
+ * Removed `php ` prefix invocation from help messages.
+
+5.1.0
+-----
+
+ * `Command::setHidden()` is final since Symfony 5.1
+ * Add `SingleCommandApplication`
+ * Add `Cursor` class
+
+5.0.0
+-----
+
+ * removed support for finding hidden commands using an abbreviation, use the full name instead
+ * removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()`
+ * removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()`
+ * removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()`
+ * removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()`
+ * removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()`
+ * removed support for returning `null` from `Command::execute()`, return `0` instead
+ * `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument
+ * `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface`
+ for its `dispatcher` argument
+ * renamed `Application::renderException()` and `Application::doRenderException()`
+ to `renderThrowable()` and `doRenderThrowable()` respectively.
+
+4.4.0
+-----
+
+ * deprecated finding hidden commands using an abbreviation, use the full name instead
+ * added `Question::setTrimmable` default to true to allow the answer to be trimmed
+ * added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar`
+ * `Application` implements `ResetInterface`
+ * marked all dispatched event classes as `@final`
+ * added support for displaying table horizontally
+ * deprecated returning `null` from `Command::execute()`, return `0` instead
+ * Deprecated the `Application::renderException()` and `Application::doRenderException()` methods,
+ use `renderThrowable()` and `doRenderThrowable()` instead.
+ * added support for the `NO_COLOR` env var (https://no-color.org/)
+
+4.3.0
+-----
+
+ * added support for hyperlinks
+ * added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating
+ * added `Question::setAutocompleterCallback()` to provide a callback function
+ that dynamically generates suggestions as the user types
+
+4.2.0
+-----
+
+ * allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to
+ `ProcessHelper::run()` to pass environment variables
+ * deprecated passing a command as a string to `ProcessHelper::run()`,
+ pass it the command as an array of its arguments instead
+ * made the `ProcessHelper` class final
+ * added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`)
+ * added `capture_stderr_separately` option to `CommandTester::execute()`
+
+4.1.0
+-----
+
+ * added option to run suggested command if command is not found and only 1 alternative is available
+ * added option to modify console output and print multiple modifiable sections
+ * added support for iterable messages in output `write` and `writeln` methods
+
+4.0.0
+-----
+
+ * `OutputFormatter` throws an exception when unknown options are used
+ * removed `QuestionHelper::setInputStream()/getInputStream()`
+ * removed `Application::getTerminalWidth()/getTerminalHeight()` and
+ `Application::setTerminalDimensions()/getTerminalDimensions()`
+ * removed `ConsoleExceptionEvent`
+ * removed `ConsoleEvents::EXCEPTION`
+
+3.4.0
+-----
+
+ * added `SHELL_VERBOSITY` env var to control verbosity
+ * added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11
+ `ContainerCommandLoader` for commands lazy-loading
+ * added a case-insensitive command name matching fallback
+ * added static `Command::$defaultName/getDefaultName()`, allowing for
+ commands to be registered at compile time in the application command loader.
+ Setting the `$defaultName` property avoids the need for filling the `command`
+ attribute on the `console.command` tag when using `AddConsoleCommandPass`.
+
+3.3.0
+-----
+
+ * added `ExceptionListener`
+ * added `AddConsoleCommandPass` (originally in FrameworkBundle)
+ * [BC BREAK] `Input::getOption()` no longer returns the default value for options
+ with value optional explicitly passed empty
+ * added console.error event to catch exceptions thrown by other listeners
+ * deprecated console.exception event in favor of console.error
+ * added ability to handle `CommandNotFoundException` through the
+ `console.error` event
+ * deprecated default validation in `SymfonyQuestionHelper::ask`
+
+3.2.0
+------
+
+ * added `setInputs()` method to CommandTester for ease testing of commands expecting inputs
+ * added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface)
+ * added StreamableInputInterface
+ * added LockableTrait
+
+3.1.0
+-----
+
+ * added truncate method to FormatterHelper
+ * added setColumnWidth(s) method to Table
+
+2.8.3
+-----
+
+ * remove readline support from the question helper as it caused issues
+
+2.8.0
+-----
+
+ * use readline for user input in the question helper when available to allow
+ the use of arrow keys
+
+2.6.0
+-----
+
+ * added a Process helper
+ * added a DebugFormatter helper
+
+2.5.0
+-----
+
+ * deprecated the dialog helper (use the question helper instead)
+ * deprecated TableHelper in favor of Table
+ * deprecated ProgressHelper in favor of ProgressBar
+ * added ConsoleLogger
+ * added a question helper
+ * added a way to set the process name of a command
+ * added a way to set a default command instead of `ListCommand`
+
+2.4.0
+-----
+
+ * added a way to force terminal dimensions
+ * added a convenient method to detect verbosity level
+ * [BC BREAK] made descriptors use output instead of returning a string
+
+2.3.0
+-----
+
+ * added multiselect support to the select dialog helper
+ * added Table Helper for tabular data rendering
+ * added support for events in `Application`
+ * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()`
+ * added a way to set the progress bar progress via the `setCurrent` method
+ * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'`
+ * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG
+
+2.2.0
+-----
+
+ * added support for colorization on Windows via ConEmu
+ * add a method to Dialog Helper to ask for a question and hide the response
+ * added support for interactive selections in console (DialogHelper::select())
+ * added support for autocompletion as you type in Dialog Helper
+
+2.1.0
+-----
+
+ * added ConsoleOutputInterface
+ * added the possibility to disable a command (Command::isEnabled())
+ * added suggestions when a command does not exist
+ * added a --raw option to the list command
+ * added support for STDERR in the console output class (errors are now sent
+ to STDERR)
+ * made the defaults (helper set, commands, input definition) in Application
+ more easily customizable
+ * added support for the shell even if readline is not available
+ * added support for process isolation in Symfony shell via
+ `--process-isolation` switch
+ * added support for `--`, which disables options parsing after that point
+ (tokens will be parsed as arguments)
diff --git a/vendor/symfony/console/CI/GithubActionReporter.php b/vendor/symfony/console/CI/GithubActionReporter.php
new file mode 100644
index 0000000..a15c1ff
--- /dev/null
+++ b/vendor/symfony/console/CI/GithubActionReporter.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\CI;
+
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Utility class for Github actions.
+ *
+ * @author Maxime Steinhausser
+ */
+class GithubActionReporter
+{
+ private $output;
+
+ /**
+ * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85
+ */
+ private const ESCAPED_DATA = [
+ '%' => '%25',
+ "\r" => '%0D',
+ "\n" => '%0A',
+ ];
+
+ /**
+ * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94
+ */
+ private const ESCAPED_PROPERTIES = [
+ '%' => '%25',
+ "\r" => '%0D',
+ "\n" => '%0A',
+ ':' => '%3A',
+ ',' => '%2C',
+ ];
+
+ public function __construct(OutputInterface $output)
+ {
+ $this->output = $output;
+ }
+
+ public static function isGithubActionEnvironment(): bool
+ {
+ return false !== getenv('GITHUB_ACTIONS');
+ }
+
+ /**
+ * Output an error using the Github annotations format.
+ *
+ * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
+ */
+ public function error(string $message, string $file = null, int $line = null, int $col = null): void
+ {
+ $this->log('error', $message, $file, $line, $col);
+ }
+
+ /**
+ * Output a warning using the Github annotations format.
+ *
+ * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
+ */
+ public function warning(string $message, string $file = null, int $line = null, int $col = null): void
+ {
+ $this->log('warning', $message, $file, $line, $col);
+ }
+
+ /**
+ * Output a debug log using the Github annotations format.
+ *
+ * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
+ */
+ public function debug(string $message, string $file = null, int $line = null, int $col = null): void
+ {
+ $this->log('debug', $message, $file, $line, $col);
+ }
+
+ private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void
+ {
+ // Some values must be encoded.
+ $message = strtr($message, self::ESCAPED_DATA);
+
+ if (!$file) {
+ // No file provided, output the message solely:
+ $this->output->writeln(sprintf('::%s::%s', $type, $message));
+
+ return;
+ }
+
+ $this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message));
+ }
+}
diff --git a/vendor/symfony/console/Color.php b/vendor/symfony/console/Color.php
new file mode 100644
index 0000000..22a4ce9
--- /dev/null
+++ b/vendor/symfony/console/Color.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console;
+
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+
+/**
+ * @author Fabien Potencier
+ */
+final class Color
+{
+ private const COLORS = [
+ 'black' => 0,
+ 'red' => 1,
+ 'green' => 2,
+ 'yellow' => 3,
+ 'blue' => 4,
+ 'magenta' => 5,
+ 'cyan' => 6,
+ 'white' => 7,
+ 'default' => 9,
+ ];
+
+ private const BRIGHT_COLORS = [
+ 'gray' => 0,
+ 'bright-red' => 1,
+ 'bright-green' => 2,
+ 'bright-yellow' => 3,
+ 'bright-blue' => 4,
+ 'bright-magenta' => 5,
+ 'bright-cyan' => 6,
+ 'bright-white' => 7,
+ ];
+
+ private const AVAILABLE_OPTIONS = [
+ 'bold' => ['set' => 1, 'unset' => 22],
+ 'underscore' => ['set' => 4, 'unset' => 24],
+ 'blink' => ['set' => 5, 'unset' => 25],
+ 'reverse' => ['set' => 7, 'unset' => 27],
+ 'conceal' => ['set' => 8, 'unset' => 28],
+ ];
+
+ private $foreground;
+ private $background;
+ private $options = [];
+
+ public function __construct(string $foreground = '', string $background = '', array $options = [])
+ {
+ $this->foreground = $this->parseColor($foreground);
+ $this->background = $this->parseColor($background, true);
+
+ foreach ($options as $option) {
+ if (!isset(self::AVAILABLE_OPTIONS[$option])) {
+ throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
+ }
+
+ $this->options[$option] = self::AVAILABLE_OPTIONS[$option];
+ }
+ }
+
+ public function apply(string $text): string
+ {
+ return $this->set().$text.$this->unset();
+ }
+
+ public function set(): string
+ {
+ $setCodes = [];
+ if ('' !== $this->foreground) {
+ $setCodes[] = $this->foreground;
+ }
+ if ('' !== $this->background) {
+ $setCodes[] = $this->background;
+ }
+ foreach ($this->options as $option) {
+ $setCodes[] = $option['set'];
+ }
+ if (0 === \count($setCodes)) {
+ return '';
+ }
+
+ return sprintf("\033[%sm", implode(';', $setCodes));
+ }
+
+ public function unset(): string
+ {
+ $unsetCodes = [];
+ if ('' !== $this->foreground) {
+ $unsetCodes[] = 39;
+ }
+ if ('' !== $this->background) {
+ $unsetCodes[] = 49;
+ }
+ foreach ($this->options as $option) {
+ $unsetCodes[] = $option['unset'];
+ }
+ if (0 === \count($unsetCodes)) {
+ return '';
+ }
+
+ return sprintf("\033[%sm", implode(';', $unsetCodes));
+ }
+
+ private function parseColor(string $color, bool $background = false): string
+ {
+ if ('' === $color) {
+ return '';
+ }
+
+ if ('#' === $color[0]) {
+ $color = substr($color, 1);
+
+ if (3 === \strlen($color)) {
+ $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2];
+ }
+
+ if (6 !== \strlen($color)) {
+ throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color));
+ }
+
+ return ($background ? '4' : '3').$this->convertHexColorToAnsi(hexdec($color));
+ }
+
+ if (isset(self::COLORS[$color])) {
+ return ($background ? '4' : '3').self::COLORS[$color];
+ }
+
+ if (isset(self::BRIGHT_COLORS[$color])) {
+ return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];
+ }
+
+ throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
+ }
+
+ private function convertHexColorToAnsi(int $color): string
+ {
+ $r = ($color >> 16) & 255;
+ $g = ($color >> 8) & 255;
+ $b = $color & 255;
+
+ // see https://github.com/termstandard/colors/ for more information about true color support
+ if ('truecolor' !== getenv('COLORTERM')) {
+ return (string) $this->degradeHexColorToAnsi($r, $g, $b);
+ }
+
+ return sprintf('8;2;%d;%d;%d', $r, $g, $b);
+ }
+
+ private function degradeHexColorToAnsi(int $r, int $g, int $b): int
+ {
+ if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
+ return 0;
+ }
+
+ return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255);
+ }
+
+ private function getSaturation(int $r, int $g, int $b): int
+ {
+ $r = $r / 255;
+ $g = $g / 255;
+ $b = $b / 255;
+ $v = max($r, $g, $b);
+
+ if (0 === $diff = $v - min($r, $g, $b)) {
+ return 0;
+ }
+
+ return (int) $diff * 100 / $v;
+ }
+}
diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php
new file mode 100644
index 0000000..146f601
--- /dev/null
+++ b/vendor/symfony/console/Command/Command.php
@@ -0,0 +1,710 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Exception\ExceptionInterface;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Exception\LogicException;
+use Symfony\Component\Console\Helper\HelperSet;
+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;
+
+/**
+ * Base class for all commands.
+ *
+ * @author Fabien Potencier
+ */
+class Command
+{
+ // see https://tldp.org/LDP/abs/html/exitcodes.html
+ public const SUCCESS = 0;
+ public const FAILURE = 1;
+ public const INVALID = 2;
+
+ /**
+ * @var string|null The default command name
+ */
+ protected static $defaultName;
+
+ /**
+ * @var string|null The default command description
+ */
+ protected static $defaultDescription;
+
+ private $application;
+ private $name;
+ private $processTitle;
+ private $aliases = [];
+ private $definition;
+ private $hidden = false;
+ private $help = '';
+ private $description = '';
+ private $fullDefinition;
+ private $ignoreValidationErrors = false;
+ private $code;
+ private $synopsis = [];
+ private $usages = [];
+ private $helperSet;
+
+ /**
+ * @return string|null
+ */
+ public static function getDefaultName()
+ {
+ $class = static::class;
+
+ if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
+ return $attribute[0]->newInstance()->name;
+ }
+
+ $r = new \ReflectionProperty($class, 'defaultName');
+
+ return $class === $r->class ? static::$defaultName : null;
+ }
+
+ public static function getDefaultDescription(): ?string
+ {
+ $class = static::class;
+
+ if (\PHP_VERSION_ID >= 80000 && $attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
+ return $attribute[0]->newInstance()->description;
+ }
+
+ $r = new \ReflectionProperty($class, 'defaultDescription');
+
+ return $class === $r->class ? static::$defaultDescription : null;
+ }
+
+ /**
+ * @param string|null $name The name of the command; passing null means it must be set in configure()
+ *
+ * @throws LogicException When the command name is empty
+ */
+ public function __construct(string $name = null)
+ {
+ $this->definition = new InputDefinition();
+
+ if (null === $name && null !== $name = static::getDefaultName()) {
+ $aliases = explode('|', $name);
+
+ if ('' === $name = array_shift($aliases)) {
+ $this->setHidden(true);
+ $name = array_shift($aliases);
+ }
+
+ $this->setAliases($aliases);
+ }
+
+ if (null !== $name) {
+ $this->setName($name);
+ }
+
+ if ('' === $this->description) {
+ $this->setDescription(static::getDefaultDescription() ?? '');
+ }
+
+ $this->configure();
+ }
+
+ /**
+ * Ignores validation errors.
+ *
+ * This is mainly useful for the help command.
+ */
+ public function ignoreValidationErrors()
+ {
+ $this->ignoreValidationErrors = true;
+ }
+
+ public function setApplication(Application $application = null)
+ {
+ $this->application = $application;
+ if ($application) {
+ $this->setHelperSet($application->getHelperSet());
+ } else {
+ $this->helperSet = null;
+ }
+
+ $this->fullDefinition = null;
+ }
+
+ public function setHelperSet(HelperSet $helperSet)
+ {
+ $this->helperSet = $helperSet;
+ }
+
+ /**
+ * Gets the helper set.
+ *
+ * @return HelperSet|null
+ */
+ public function getHelperSet()
+ {
+ return $this->helperSet;
+ }
+
+ /**
+ * Gets the application instance for this command.
+ *
+ * @return Application|null
+ */
+ public function getApplication()
+ {
+ return $this->application;
+ }
+
+ /**
+ * Checks whether the command is enabled or not in the current environment.
+ *
+ * Override this to check for x or y and return false if the command cannot
+ * run properly under the current conditions.
+ *
+ * @return bool
+ */
+ public function isEnabled()
+ {
+ return true;
+ }
+
+ /**
+ * Configures the current command.
+ */
+ protected function configure()
+ {
+ }
+
+ /**
+ * Executes the current command.
+ *
+ * This method is not abstract because you can use this class
+ * as a concrete class. In this case, instead of defining the
+ * execute() method, you set the code to execute by passing
+ * a Closure to the setCode() method.
+ *
+ * @return int 0 if everything went fine, or an exit code
+ *
+ * @throws LogicException When this abstract method is not implemented
+ *
+ * @see setCode()
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ throw new LogicException('You must override the execute() method in the concrete command class.');
+ }
+
+ /**
+ * Interacts with the user.
+ *
+ * This method is executed before the InputDefinition is validated.
+ * This means that this is the only place where the command can
+ * interactively ask for values of missing required arguments.
+ */
+ protected function interact(InputInterface $input, OutputInterface $output)
+ {
+ }
+
+ /**
+ * Initializes the command after the input has been bound and before the input
+ * is validated.
+ *
+ * This is mainly useful when a lot of commands extends one main command
+ * where some things need to be initialized based on the input arguments and options.
+ *
+ * @see InputInterface::bind()
+ * @see InputInterface::validate()
+ */
+ protected function initialize(InputInterface $input, OutputInterface $output)
+ {
+ }
+
+ /**
+ * Runs the command.
+ *
+ * The code to execute is either defined directly with the
+ * setCode() method or by overriding the execute() method
+ * in a sub-class.
+ *
+ * @return int The command exit code
+ *
+ * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
+ *
+ * @see setCode()
+ * @see execute()
+ */
+ public function run(InputInterface $input, OutputInterface $output)
+ {
+ // add the application arguments and options
+ $this->mergeApplicationDefinition();
+
+ // bind the input against the command specific arguments/options
+ try {
+ $input->bind($this->getDefinition());
+ } catch (ExceptionInterface $e) {
+ if (!$this->ignoreValidationErrors) {
+ throw $e;
+ }
+ }
+
+ $this->initialize($input, $output);
+
+ if (null !== $this->processTitle) {
+ if (\function_exists('cli_set_process_title')) {
+ if (!@cli_set_process_title($this->processTitle)) {
+ if ('Darwin' === \PHP_OS) {
+ $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE);
+ } else {
+ cli_set_process_title($this->processTitle);
+ }
+ }
+ } elseif (\function_exists('setproctitle')) {
+ setproctitle($this->processTitle);
+ } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
+ $output->writeln('Install the proctitle PECL to be able to change the process title.');
+ }
+ }
+
+ if ($input->isInteractive()) {
+ $this->interact($input, $output);
+ }
+
+ // The command name argument is often omitted when a command is executed directly with its run() method.
+ // It would fail the validation if we didn't make sure the command argument is present,
+ // since it's required by the application.
+ if ($input->hasArgument('command') && null === $input->getArgument('command')) {
+ $input->setArgument('command', $this->getName());
+ }
+
+ $input->validate();
+
+ if ($this->code) {
+ $statusCode = ($this->code)($input, $output);
+ } else {
+ $statusCode = $this->execute($input, $output);
+
+ if (!\is_int($statusCode)) {
+ throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
+ }
+ }
+
+ return is_numeric($statusCode) ? (int) $statusCode : 0;
+ }
+
+ /**
+ * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
+ */
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ }
+
+ /**
+ * Sets the code to execute when running this command.
+ *
+ * If this method is used, it overrides the code defined
+ * in the execute() method.
+ *
+ * @param callable $code A callable(InputInterface $input, OutputInterface $output)
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException
+ *
+ * @see execute()
+ */
+ public function setCode(callable $code)
+ {
+ if ($code instanceof \Closure) {
+ $r = new \ReflectionFunction($code);
+ if (null === $r->getClosureThis()) {
+ set_error_handler(static function () {});
+ try {
+ if ($c = \Closure::bind($code, $this)) {
+ $code = $c;
+ }
+ } finally {
+ restore_error_handler();
+ }
+ }
+ }
+
+ $this->code = $code;
+
+ return $this;
+ }
+
+ /**
+ * Merges the application definition with the command definition.
+ *
+ * This method is not part of public API and should not be used directly.
+ *
+ * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
+ *
+ * @internal
+ */
+ public function mergeApplicationDefinition(bool $mergeArgs = true)
+ {
+ if (null === $this->application) {
+ return;
+ }
+
+ $this->fullDefinition = new InputDefinition();
+ $this->fullDefinition->setOptions($this->definition->getOptions());
+ $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());
+
+ if ($mergeArgs) {
+ $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
+ $this->fullDefinition->addArguments($this->definition->getArguments());
+ } else {
+ $this->fullDefinition->setArguments($this->definition->getArguments());
+ }
+ }
+
+ /**
+ * Sets an array of argument and option instances.
+ *
+ * @param array|InputDefinition $definition An array of argument and option instances or a definition instance
+ *
+ * @return $this
+ */
+ public function setDefinition($definition)
+ {
+ if ($definition instanceof InputDefinition) {
+ $this->definition = $definition;
+ } else {
+ $this->definition->setDefinition($definition);
+ }
+
+ $this->fullDefinition = null;
+
+ return $this;
+ }
+
+ /**
+ * Gets the InputDefinition attached to this Command.
+ *
+ * @return InputDefinition
+ */
+ public function getDefinition()
+ {
+ return $this->fullDefinition ?? $this->getNativeDefinition();
+ }
+
+ /**
+ * Gets the InputDefinition to be used to create representations of this Command.
+ *
+ * Can be overridden to provide the original command representation when it would otherwise
+ * be changed by merging with the application InputDefinition.
+ *
+ * This method is not part of public API and should not be used directly.
+ *
+ * @return InputDefinition
+ */
+ public function getNativeDefinition()
+ {
+ if (null === $this->definition) {
+ throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
+ }
+
+ return $this->definition;
+ }
+
+ /**
+ * Adds an argument.
+ *
+ * @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
+ * @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
+ *
+ * @throws InvalidArgumentException When argument mode is not valid
+ *
+ * @return $this
+ */
+ public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
+ {
+ $this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
+ if (null !== $this->fullDefinition) {
+ $this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds an option.
+ *
+ * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
+ * @param int|null $mode The option mode: One of the InputOption::VALUE_* constants
+ * @param mixed $default The default value (must be null for InputOption::VALUE_NONE)
+ *
+ * @throws InvalidArgumentException If option mode is invalid or incompatible
+ *
+ * @return $this
+ */
+ public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
+ {
+ $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
+ if (null !== $this->fullDefinition) {
+ $this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the name of the command.
+ *
+ * This method can set both the namespace and the name if
+ * you separate them by a colon (:)
+ *
+ * $command->setName('foo:bar');
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException When the name is invalid
+ */
+ public function setName(string $name)
+ {
+ $this->validateName($name);
+
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Sets the process title of the command.
+ *
+ * This feature should be used only when creating a long process command,
+ * like a daemon.
+ *
+ * @return $this
+ */
+ public function setProcessTitle(string $title)
+ {
+ $this->processTitle = $title;
+
+ return $this;
+ }
+
+ /**
+ * Returns the command name.
+ *
+ * @return string|null
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param bool $hidden Whether or not the command should be hidden from the list of commands
+ * The default value will be true in Symfony 6.0
+ *
+ * @return $this
+ *
+ * @final since Symfony 5.1
+ */
+ public function setHidden(bool $hidden /*= true*/)
+ {
+ $this->hidden = $hidden;
+
+ return $this;
+ }
+
+ /**
+ * @return bool whether the command should be publicly shown or not
+ */
+ public function isHidden()
+ {
+ return $this->hidden;
+ }
+
+ /**
+ * Sets the description for the command.
+ *
+ * @return $this
+ */
+ public function setDescription(string $description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Returns the description for the command.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Sets the help for the command.
+ *
+ * @return $this
+ */
+ public function setHelp(string $help)
+ {
+ $this->help = $help;
+
+ return $this;
+ }
+
+ /**
+ * Returns the help for the command.
+ *
+ * @return string
+ */
+ public function getHelp()
+ {
+ return $this->help;
+ }
+
+ /**
+ * Returns the processed help for the command replacing the %command.name% and
+ * %command.full_name% patterns with the real values dynamically.
+ *
+ * @return string
+ */
+ public function getProcessedHelp()
+ {
+ $name = $this->name;
+ $isSingleCommand = $this->application && $this->application->isSingleCommand();
+
+ $placeholders = [
+ '%command.name%',
+ '%command.full_name%',
+ ];
+ $replacements = [
+ $name,
+ $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
+ ];
+
+ return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
+ }
+
+ /**
+ * Sets the aliases for the command.
+ *
+ * @param string[] $aliases An array of aliases for the command
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException When an alias is invalid
+ */
+ public function setAliases(iterable $aliases)
+ {
+ $list = [];
+
+ foreach ($aliases as $alias) {
+ $this->validateName($alias);
+ $list[] = $alias;
+ }
+
+ $this->aliases = \is_array($aliases) ? $aliases : $list;
+
+ return $this;
+ }
+
+ /**
+ * Returns the aliases for the command.
+ *
+ * @return array
+ */
+ public function getAliases()
+ {
+ return $this->aliases;
+ }
+
+ /**
+ * Returns the synopsis for the command.
+ *
+ * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
+ *
+ * @return string
+ */
+ public function getSynopsis(bool $short = false)
+ {
+ $key = $short ? 'short' : 'long';
+
+ if (!isset($this->synopsis[$key])) {
+ $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
+ }
+
+ return $this->synopsis[$key];
+ }
+
+ /**
+ * Add a command usage example, it'll be prefixed with the command name.
+ *
+ * @return $this
+ */
+ public function addUsage(string $usage)
+ {
+ if (!str_starts_with($usage, $this->name)) {
+ $usage = sprintf('%s %s', $this->name, $usage);
+ }
+
+ $this->usages[] = $usage;
+
+ return $this;
+ }
+
+ /**
+ * Returns alternative usages of the command.
+ *
+ * @return array
+ */
+ public function getUsages()
+ {
+ return $this->usages;
+ }
+
+ /**
+ * Gets a helper instance by name.
+ *
+ * @return mixed
+ *
+ * @throws LogicException if no HelperSet is defined
+ * @throws InvalidArgumentException if the helper is not defined
+ */
+ public function getHelper(string $name)
+ {
+ if (null === $this->helperSet) {
+ throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
+ }
+
+ return $this->helperSet->get($name);
+ }
+
+ /**
+ * Validates a command name.
+ *
+ * It must be non-empty and parts can optionally be separated by ":".
+ *
+ * @throws InvalidArgumentException When the name is invalid
+ */
+ private function validateName(string $name)
+ {
+ if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
+ throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
+ }
+ }
+}
diff --git a/vendor/symfony/console/Command/CompleteCommand.php b/vendor/symfony/console/Command/CompleteCommand.php
new file mode 100644
index 0000000..97357d6
--- /dev/null
+++ b/vendor/symfony/console/Command/CompleteCommand.php
@@ -0,0 +1,204 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
+use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+use Symfony\Component\Console\Exception\ExceptionInterface;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Responsible for providing the values to the shell completion.
+ *
+ * @author Wouter de Jong
+ */
+final class CompleteCommand extends Command
+{
+ protected static $defaultName = '|_complete';
+ protected static $defaultDescription = 'Internal command to provide shell completion suggestions';
+
+ private $completionOutputs;
+
+ private $isDebug = false;
+
+ /**
+ * @param array> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value
+ */
+ public function __construct(array $completionOutputs = [])
+ {
+ // must be set before the parent constructor, as the property value is used in configure()
+ $this->completionOutputs = $completionOutputs + ['bash' => BashCompletionOutput::class];
+
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this
+ ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
+ ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
+ ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
+ ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'The version of the completion script')
+ ;
+ }
+
+ protected function initialize(InputInterface $input, OutputInterface $output)
+ {
+ $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOLEAN);
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ // uncomment when a bugfix or BC break has been introduced in the shell completion scripts
+ //$version = $input->getOption('symfony');
+ //if ($version && version_compare($version, 'x.y', '>=')) {
+ // $message = sprintf('Completion script version is not supported ("%s" given, ">=x.y" required).', $version);
+ // $this->log($message);
+
+ // $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');
+
+ // return 126;
+ //}
+
+ $shell = $input->getOption('shell');
+ if (!$shell) {
+ throw new \RuntimeException('The "--shell" option must be set.');
+ }
+
+ if (!$completionOutput = $this->completionOutputs[$shell] ?? false) {
+ throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs))));
+ }
+
+ $completionInput = $this->createCompletionInput($input);
+ $suggestions = new CompletionSuggestions();
+
+ $this->log([
+ '',
+ ''.date('Y-m-d H:i:s').'>',
+ 'Input:> ("|" indicates the cursor position)>',
+ ' '.(string) $completionInput,
+ 'Command:>',
+ ' '.(string) implode(' ', $_SERVER['argv']),
+ 'Messages:>',
+ ]);
+
+ $command = $this->findCommand($completionInput, $output);
+ if (null === $command) {
+ $this->log(' No command found, completing using the Application class.');
+
+ $this->getApplication()->complete($completionInput, $suggestions);
+ } elseif (
+ $completionInput->mustSuggestArgumentValuesFor('command')
+ && $command->getName() !== $completionInput->getCompletionValue()
+ ) {
+ $this->log(' No command found, completing using the Application class.');
+
+ // expand shortcut names ("cache:cl") into their full name ("cache:clear")
+ $suggestions->suggestValue($command->getName());
+ } else {
+ $command->mergeApplicationDefinition();
+ $completionInput->bind($command->getDefinition());
+
+ if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
+ $this->log(' Completing option names for the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'> command.');
+
+ $suggestions->suggestOptions($command->getDefinition()->getOptions());
+ } else {
+ $this->log([
+ ' Completing using the '.\get_class($command instanceof LazyCommand ? $command->getCommand() : $command).'> class.',
+ ' Completing '.$completionInput->getCompletionType().'> for '.$completionInput->getCompletionName().'>',
+ ]);
+ if (null !== $compval = $completionInput->getCompletionValue()) {
+ $this->log(' Current value: '.$compval.'>');
+ }
+
+ $command->complete($completionInput, $suggestions);
+ }
+ }
+
+ /** @var CompletionOutputInterface $completionOutput */
+ $completionOutput = new $completionOutput();
+
+ $this->log('Suggestions:>');
+ if ($options = $suggestions->getOptionSuggestions()) {
+ $this->log(' --'.implode(' --', array_map(function ($o) { return $o->getName(); }, $options)));
+ } elseif ($values = $suggestions->getValueSuggestions()) {
+ $this->log(' '.implode(' ', $values));
+ } else {
+ $this->log(' No suggestions were provided>');
+ }
+
+ $completionOutput->write($suggestions, $output);
+ } catch (\Throwable $e) {
+ $this->log([
+ 'Error!',
+ (string) $e,
+ ]);
+
+ if ($output->isDebug()) {
+ throw $e;
+ }
+
+ return self::FAILURE;
+ }
+
+ return self::SUCCESS;
+ }
+
+ private function createCompletionInput(InputInterface $input): CompletionInput
+ {
+ $currentIndex = $input->getOption('current');
+ if (!$currentIndex || !ctype_digit($currentIndex)) {
+ throw new \RuntimeException('The "--current" option must be set and it must be an integer.');
+ }
+
+ $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex);
+
+ try {
+ $completionInput->bind($this->getApplication()->getDefinition());
+ } catch (ExceptionInterface $e) {
+ }
+
+ return $completionInput;
+ }
+
+ private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command
+ {
+ try {
+ $inputName = $completionInput->getFirstArgument();
+ if (null === $inputName) {
+ return null;
+ }
+
+ return $this->getApplication()->find($inputName);
+ } catch (CommandNotFoundException $e) {
+ }
+
+ return null;
+ }
+
+ private function log($messages): void
+ {
+ if (!$this->isDebug) {
+ return;
+ }
+
+ $commandName = basename($_SERVER['argv'][0]);
+ file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND);
+ }
+}
diff --git a/vendor/symfony/console/Command/DumpCompletionCommand.php b/vendor/symfony/console/Command/DumpCompletionCommand.php
new file mode 100644
index 0000000..64a8005
--- /dev/null
+++ b/vendor/symfony/console/Command/DumpCompletionCommand.php
@@ -0,0 +1,133 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * Dumps the completion script for the current shell.
+ *
+ * @author Wouter de Jong
+ */
+final class DumpCompletionCommand extends Command
+{
+ protected static $defaultName = 'completion';
+ protected static $defaultDescription = 'Dump the shell completion script';
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('shell')) {
+ $suggestions->suggestValues($this->getSupportedShells());
+ }
+ }
+
+ protected function configure()
+ {
+ $fullCommand = $_SERVER['PHP_SELF'];
+ $commandName = basename($fullCommand);
+ $fullCommand = realpath($fullCommand) ?: $fullCommand;
+
+ $this
+ ->setHelp(<<%command.name%> command dumps the shell completion script required
+to use shell autocompletion (currently only bash completion is supported).
+
+Static installation
+------------------->
+
+Dump the script to a global completion file and restart your shell:
+
+ %command.full_name% bash | sudo tee /etc/bash_completion.d/${commandName}>
+
+Or dump the script to a local file and source it:
+
+ %command.full_name% bash > completion.sh>
+
+ # source the file whenever you use the project>
+ source completion.sh>
+
+ # or add this line at the end of your "~/.bashrc" file:>
+ source /path/to/completion.sh>
+
+Dynamic installation
+-------------------->
+
+Add this to the end of your shell configuration file (e.g. "~/.bashrc">):
+
+ eval "$(${fullCommand} completion bash)">
+EOH
+ )
+ ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given')
+ ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $commandName = basename($_SERVER['argv'][0]);
+
+ if ($input->getOption('debug')) {
+ $this->tailDebugLog($commandName, $output);
+
+ return self::SUCCESS;
+ }
+
+ $shell = $input->getArgument('shell') ?? self::guessShell();
+ $completionFile = __DIR__.'/../Resources/completion.'.$shell;
+ if (!file_exists($completionFile)) {
+ $supportedShells = $this->getSupportedShells();
+
+ ($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output)
+ ->writeln(sprintf('Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").>', $shell, implode('", "', $supportedShells)));
+
+ return self::INVALID;
+ }
+
+ $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, $this->getApplication()->getVersion()], file_get_contents($completionFile)));
+
+ return self::SUCCESS;
+ }
+
+ private static function guessShell(): string
+ {
+ return basename($_SERVER['SHELL'] ?? '');
+ }
+
+ private function tailDebugLog(string $commandName, OutputInterface $output): void
+ {
+ $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log';
+ if (!file_exists($debugFile)) {
+ touch($debugFile);
+ }
+ $process = new Process(['tail', '-f', $debugFile], null, null, null, 0);
+ $process->run(function (string $type, string $line) use ($output): void {
+ $output->write($line);
+ });
+ }
+
+ /**
+ * @return string[]
+ */
+ private function getSupportedShells(): array
+ {
+ return array_map(function ($f) {
+ return pathinfo($f, \PATHINFO_EXTENSION);
+ }, glob(__DIR__.'/../Resources/completion.*'));
+ }
+}
diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php
new file mode 100644
index 0000000..c66ef46
--- /dev/null
+++ b/vendor/symfony/console/Command/HelpCommand.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+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\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * HelpCommand displays the help for a given command.
+ *
+ * @author Fabien Potencier
+ */
+class HelpCommand extends Command
+{
+ private $command;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this->ignoreValidationErrors();
+
+ $this
+ ->setName('help')
+ ->setDefinition([
+ new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
+ new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
+ new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
+ ])
+ ->setDescription('Display help for a command')
+ ->setHelp(<<<'EOF'
+The %command.name% command displays help for a given command:
+
+ %command.full_name% list
+
+You can also output the help in other formats by using the --format option:
+
+ %command.full_name% --format=xml list
+
+To display the list of available commands, please use the list command.
+EOF
+ )
+ ;
+ }
+
+ public function setCommand(Command $command)
+ {
+ $this->command = $command;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ if (null === $this->command) {
+ $this->command = $this->getApplication()->find($input->getArgument('command_name'));
+ }
+
+ $helper = new DescriptorHelper();
+ $helper->describe($output, $this->command, [
+ 'format' => $input->getOption('format'),
+ 'raw_text' => $input->getOption('raw'),
+ ]);
+
+ $this->command = null;
+
+ return 0;
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('command_name')) {
+ $descriptor = new ApplicationDescription($this->getApplication());
+ $suggestions->suggestValues(array_keys($descriptor->getCommands()));
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('format')) {
+ $helper = new DescriptorHelper();
+ $suggestions->suggestValues($helper->getFormats());
+ }
+ }
+}
diff --git a/vendor/symfony/console/Command/LazyCommand.php b/vendor/symfony/console/Command/LazyCommand.php
new file mode 100644
index 0000000..e576ad0
--- /dev/null
+++ b/vendor/symfony/console/Command/LazyCommand.php
@@ -0,0 +1,218 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Helper\HelperSet;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+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/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php
new file mode 100644
index 0000000..f04a4ef
--- /dev/null
+++ b/vendor/symfony/console/Command/ListCommand.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+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\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * ListCommand displays the list of all available commands for the application.
+ *
+ * @author Fabien Potencier
+ */
+class ListCommand extends Command
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function configure()
+ {
+ $this
+ ->setName('list')
+ ->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 %command.name% command lists all commands:
+
+ %command.full_name%
+
+You can also display the commands for a specific namespace:
+
+ %command.full_name% test
+
+You can also output the information in other formats by using the --format option:
+
+ %command.full_name% --format=xml
+
+It's also possible to get raw list of commands (useful for embedding command runner):
+
+ %command.full_name% --raw
+EOF
+ )
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $helper = new DescriptorHelper();
+ $helper->describe($output, $this->getApplication(), [
+ 'format' => $input->getOption('format'),
+ 'raw_text' => $input->getOption('raw'),
+ 'namespace' => $input->getArgument('namespace'),
+ 'short' => $input->getOption('short'),
+ ]);
+
+ return 0;
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('namespace')) {
+ $descriptor = new ApplicationDescription($this->getApplication());
+ $suggestions->suggestValues(array_keys($descriptor->getNamespaces()));
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('format')) {
+ $helper = new DescriptorHelper();
+ $suggestions->suggestValues($helper->getFormats());
+ }
+ }
+}
diff --git a/vendor/symfony/console/Command/LockableTrait.php b/vendor/symfony/console/Command/LockableTrait.php
new file mode 100644
index 0000000..b1856dc
--- /dev/null
+++ b/vendor/symfony/console/Command/LockableTrait.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Command;
+
+use Symfony\Component\Console\Exception\LogicException;
+use Symfony\Component\Lock\LockFactory;
+use Symfony\Component\Lock\LockInterface;
+use Symfony\Component\Lock\Store\FlockStore;
+use Symfony\Component\Lock\Store\SemaphoreStore;
+
+/**
+ * Basic lock feature for commands.
+ *
+ * @author Geoffrey Brier
+ */
+trait LockableTrait
+{
+ /** @var LockInterface|null */
+ private $lock;
+
+ /**
+ * Locks a command.
+ */
+ private function lock(string $name = null, bool $blocking = false): bool
+ {
+ if (!class_exists(SemaphoreStore::class)) {
+ throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
+ }
+
+ if (null !== $this->lock) {
+ throw new LogicException('A lock is already in place.');
+ }
+
+ if (SemaphoreStore::isSupported()) {
+ $store = new SemaphoreStore();
+ } else {
+ $store = new FlockStore();
+ }
+
+ $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
+ if (!$this->lock->acquire($blocking)) {
+ $this->lock = null;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Releases the command lock if there is one.
+ */
+ private function release()
+ {
+ if ($this->lock) {
+ $this->lock->release();
+ $this->lock = null;
+ }
+ }
+}
diff --git a/vendor/symfony/console/Command/SignalableCommandInterface.php b/vendor/symfony/console/Command/SignalableCommandInterface.php
new file mode 100644
index 0000000..d439728
--- /dev/null
+++ b/vendor/symfony/console/Command/SignalableCommandInterface.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\Console\Command;
+
+/**
+ * Interface for command reacting to signal.
+ *
+ * @author Grégoire Pineau
+ */
+interface SignalableCommandInterface
+{
+ /**
+ * Returns the list of signals to subscribe.
+ */
+ public function getSubscribedSignals(): array;
+
+ /**
+ * The method will be called when the application is signaled.
+ */
+ public function handleSignal(int $signal): void;
+}
diff --git a/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php
new file mode 100644
index 0000000..0adaf88
--- /dev/null
+++ b/vendor/symfony/console/CommandLoader/CommandLoaderInterface.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\CommandLoader;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+
+/**
+ * @author Robin Chalas
+ */
+interface CommandLoaderInterface
+{
+ /**
+ * Loads a command.
+ *
+ * @return Command
+ *
+ * @throws CommandNotFoundException
+ */
+ public function get(string $name);
+
+ /**
+ * Checks if a command exists.
+ *
+ * @return bool
+ */
+ public function has(string $name);
+
+ /**
+ * @return string[]
+ */
+ public function getNames();
+}
diff --git a/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php
new file mode 100644
index 0000000..ddccb3d
--- /dev/null
+++ b/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\CommandLoader;
+
+use Psr\Container\ContainerInterface;
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+
+/**
+ * Loads commands from a PSR-11 container.
+ *
+ * @author Robin Chalas
+ */
+class ContainerCommandLoader implements CommandLoaderInterface
+{
+ private $container;
+ private $commandMap;
+
+ /**
+ * @param array $commandMap An array with command names as keys and service ids as values
+ */
+ public function __construct(ContainerInterface $container, array $commandMap)
+ {
+ $this->container = $container;
+ $this->commandMap = $commandMap;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $name)
+ {
+ if (!$this->has($name)) {
+ throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
+ }
+
+ return $this->container->get($this->commandMap[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has(string $name)
+ {
+ return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNames()
+ {
+ return array_keys($this->commandMap);
+ }
+}
diff --git a/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php
new file mode 100644
index 0000000..7e2db34
--- /dev/null
+++ b/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\CommandLoader;
+
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+
+/**
+ * A simple command loader using factories to instantiate commands lazily.
+ *
+ * @author Maxime Steinhausser
+ */
+class FactoryCommandLoader implements CommandLoaderInterface
+{
+ private $factories;
+
+ /**
+ * @param callable[] $factories Indexed by command names
+ */
+ public function __construct(array $factories)
+ {
+ $this->factories = $factories;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has(string $name)
+ {
+ return isset($this->factories[$name]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $name)
+ {
+ if (!isset($this->factories[$name])) {
+ throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
+ }
+
+ $factory = $this->factories[$name];
+
+ return $factory();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getNames()
+ {
+ return array_keys($this->factories);
+ }
+}
diff --git a/vendor/symfony/console/Completion/CompletionInput.php b/vendor/symfony/console/Completion/CompletionInput.php
new file mode 100644
index 0000000..368b945
--- /dev/null
+++ b/vendor/symfony/console/Completion/CompletionInput.php
@@ -0,0 +1,249 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Completion;
+
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\ArgvInput;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * An input specialized for shell completion.
+ *
+ * This input allows unfinished option names or values and exposes what kind of
+ * completion is expected.
+ *
+ * @author Wouter de Jong
+ */
+final class CompletionInput extends ArgvInput
+{
+ public const TYPE_ARGUMENT_VALUE = 'argument_value';
+ public const TYPE_OPTION_VALUE = 'option_value';
+ public const TYPE_OPTION_NAME = 'option_name';
+ public const TYPE_NONE = 'none';
+
+ private $tokens;
+ private $currentIndex;
+ private $completionType;
+ private $completionName = null;
+ private $completionValue = '';
+
+ /**
+ * Converts a terminal string into tokens.
+ *
+ * This is required for shell completions without COMP_WORDS support.
+ */
+ public static function fromString(string $inputStr, int $currentIndex): self
+ {
+ preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?tokens = $tokens;
+ $input->currentIndex = $currentIndex;
+
+ return $input;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function bind(InputDefinition $definition): void
+ {
+ parent::bind($definition);
+
+ $relevantToken = $this->getRelevantToken();
+ if ('-' === $relevantToken[0]) {
+ // the current token is an input option: complete either option name or option value
+ [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];
+
+ $option = $this->getOptionFromToken($optionToken);
+ if (null === $option && !$this->isCursorFree()) {
+ $this->completionType = self::TYPE_OPTION_NAME;
+ $this->completionValue = $relevantToken;
+
+ return;
+ }
+
+ if (null !== $option && $option->acceptValue()) {
+ $this->completionType = self::TYPE_OPTION_VALUE;
+ $this->completionName = $option->getName();
+ $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');
+
+ return;
+ }
+ }
+
+ $previousToken = $this->tokens[$this->currentIndex - 1];
+ if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
+ // check if previous option accepted a value
+ $previousOption = $this->getOptionFromToken($previousToken);
+ if (null !== $previousOption && $previousOption->acceptValue()) {
+ $this->completionType = self::TYPE_OPTION_VALUE;
+ $this->completionName = $previousOption->getName();
+ $this->completionValue = $relevantToken;
+
+ return;
+ }
+ }
+
+ // complete argument value
+ $this->completionType = self::TYPE_ARGUMENT_VALUE;
+
+ foreach ($this->definition->getArguments() as $argumentName => $argument) {
+ if (!isset($this->arguments[$argumentName])) {
+ break;
+ }
+
+ $argumentValue = $this->arguments[$argumentName];
+ $this->completionName = $argumentName;
+ if (\is_array($argumentValue)) {
+ $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
+ } else {
+ $this->completionValue = $argumentValue;
+ }
+ }
+
+ if ($this->currentIndex >= \count($this->tokens)) {
+ if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {
+ $this->completionName = $argumentName;
+ $this->completionValue = '';
+ } else {
+ // we've reached the end
+ $this->completionType = self::TYPE_NONE;
+ $this->completionName = null;
+ $this->completionValue = '';
+ }
+ }
+ }
+
+ /**
+ * Returns the type of completion required.
+ *
+ * TYPE_ARGUMENT_VALUE when completing the value of an input argument
+ * TYPE_OPTION_VALUE when completing the value of an input option
+ * TYPE_OPTION_NAME when completing the name of an input option
+ * TYPE_NONE when nothing should be completed
+ *
+ * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component
+ */
+ public function getCompletionType(): string
+ {
+ return $this->completionType;
+ }
+
+ /**
+ * The name of the input option or argument when completing a value.
+ *
+ * @return string|null returns null when completing an option name
+ */
+ public function getCompletionName(): ?string
+ {
+ return $this->completionName;
+ }
+
+ /**
+ * The value already typed by the user (or empty string).
+ */
+ public function getCompletionValue(): string
+ {
+ return $this->completionValue;
+ }
+
+ public function mustSuggestOptionValuesFor(string $optionName): bool
+ {
+ return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
+ }
+
+ public function mustSuggestArgumentValuesFor(string $argumentName): bool
+ {
+ return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
+ }
+
+ protected function parseToken(string $token, bool $parseOptions): bool
+ {
+ try {
+ return parent::parseToken($token, $parseOptions);
+ } catch (RuntimeException $e) {
+ // suppress errors, completed input is almost never valid
+ }
+
+ return $parseOptions;
+ }
+
+ private function getOptionFromToken(string $optionToken): ?InputOption
+ {
+ $optionName = ltrim($optionToken, '-');
+ if (!$optionName) {
+ return null;
+ }
+
+ if ('-' === ($optionToken[1] ?? ' ')) {
+ // long option name
+ return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
+ }
+
+ // short option name
+ return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
+ }
+
+ /**
+ * The token of the cursor, or the last token if the cursor is at the end of the input.
+ */
+ private function getRelevantToken(): string
+ {
+ return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
+ }
+
+ /**
+ * Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
+ */
+ private function isCursorFree(): bool
+ {
+ $nrOfTokens = \count($this->tokens);
+ if ($this->currentIndex > $nrOfTokens) {
+ throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
+ }
+
+ return $this->currentIndex >= $nrOfTokens;
+ }
+
+ public function __toString()
+ {
+ $str = '';
+ foreach ($this->tokens as $i => $token) {
+ $str .= $token;
+
+ if ($this->currentIndex === $i) {
+ $str .= '|';
+ }
+
+ $str .= ' ';
+ }
+
+ if ($this->currentIndex > $i) {
+ $str .= '|';
+ }
+
+ return rtrim($str);
+ }
+}
diff --git a/vendor/symfony/console/Completion/CompletionSuggestions.php b/vendor/symfony/console/Completion/CompletionSuggestions.php
new file mode 100644
index 0000000..d8905e5
--- /dev/null
+++ b/vendor/symfony/console/Completion/CompletionSuggestions.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Completion;
+
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * Stores all completion suggestions for the current input.
+ *
+ * @author Wouter de Jong
+ */
+final class CompletionSuggestions
+{
+ private $valueSuggestions = [];
+ private $optionSuggestions = [];
+
+ /**
+ * Add a suggested value for an input option or argument.
+ *
+ * @param string|Suggestion $value
+ *
+ * @return $this
+ */
+ public function suggestValue($value): self
+ {
+ $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;
+
+ return $this;
+ }
+
+ /**
+ * Add multiple suggested values at once for an input option or argument.
+ *
+ * @param list $values
+ *
+ * @return $this
+ */
+ public function suggestValues(array $values): self
+ {
+ foreach ($values as $value) {
+ $this->suggestValue($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a suggestion for an input option name.
+ *
+ * @return $this
+ */
+ public function suggestOption(InputOption $option): self
+ {
+ $this->optionSuggestions[] = $option;
+
+ return $this;
+ }
+
+ /**
+ * Add multiple suggestions for input option names at once.
+ *
+ * @param InputOption[] $options
+ *
+ * @return $this
+ */
+ public function suggestOptions(array $options): self
+ {
+ foreach ($options as $option) {
+ $this->suggestOption($option);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return InputOption[]
+ */
+ public function getOptionSuggestions(): array
+ {
+ return $this->optionSuggestions;
+ }
+
+ /**
+ * @return Suggestion[]
+ */
+ public function getValueSuggestions(): array
+ {
+ return $this->valueSuggestions;
+ }
+}
diff --git a/vendor/symfony/console/Completion/Output/BashCompletionOutput.php b/vendor/symfony/console/Completion/Output/BashCompletionOutput.php
new file mode 100644
index 0000000..8d5ffa6
--- /dev/null
+++ b/vendor/symfony/console/Completion/Output/BashCompletionOutput.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\Console\Completion\Output;
+
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Wouter de Jong
+ */
+class BashCompletionOutput implements CompletionOutputInterface
+{
+ public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
+ {
+ $values = $suggestions->getValueSuggestions();
+ foreach ($suggestions->getOptionSuggestions() as $option) {
+ $values[] = '--'.$option->getName();
+ }
+ $output->writeln(implode("\n", $values));
+ }
+}
diff --git a/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.php
new file mode 100644
index 0000000..659e596
--- /dev/null
+++ b/vendor/symfony/console/Completion/Output/CompletionOutputInterface.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\Console\Completion\Output;
+
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion.
+ *
+ * @author Wouter de Jong
+ */
+interface CompletionOutputInterface
+{
+ public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
+}
diff --git a/vendor/symfony/console/Completion/Suggestion.php b/vendor/symfony/console/Completion/Suggestion.php
new file mode 100644
index 0000000..6c7bc4d
--- /dev/null
+++ b/vendor/symfony/console/Completion/Suggestion.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Completion;
+
+/**
+ * Represents a single suggested value.
+ *
+ * @author Wouter de Jong
+ */
+class Suggestion
+{
+ private $value;
+
+ public function __construct(string $value)
+ {
+ $this->value = $value;
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ public function __toString(): string
+ {
+ return $this->getValue();
+ }
+}
diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php
new file mode 100644
index 0000000..6ae8f32
--- /dev/null
+++ b/vendor/symfony/console/ConsoleEvents.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console;
+
+use Symfony\Component\Console\Event\ConsoleCommandEvent;
+use Symfony\Component\Console\Event\ConsoleErrorEvent;
+use Symfony\Component\Console\Event\ConsoleSignalEvent;
+use Symfony\Component\Console\Event\ConsoleTerminateEvent;
+
+/**
+ * Contains all events dispatched by an Application.
+ *
+ * @author Francesco Levorato
+ */
+final class ConsoleEvents
+{
+ /**
+ * The COMMAND event allows you to attach listeners before any command is
+ * executed by the console. It also allows you to modify the command, input and output
+ * before they are handed to the command.
+ *
+ * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
+ */
+ public const COMMAND = 'console.command';
+
+ /**
+ * The SIGNAL event allows you to perform some actions
+ * after the command execution was interrupted.
+ *
+ * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
+ */
+ public const SIGNAL = 'console.signal';
+
+ /**
+ * The TERMINATE event allows you to attach listeners after a command is
+ * executed by the console.
+ *
+ * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
+ */
+ public const TERMINATE = 'console.terminate';
+
+ /**
+ * The ERROR event occurs when an uncaught exception or error appears.
+ *
+ * This event allows you to deal with the exception/error or
+ * to modify the thrown exception.
+ *
+ * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
+ */
+ public const ERROR = 'console.error';
+
+ /**
+ * Event aliases.
+ *
+ * These aliases can be consumed by RegisterListenersPass.
+ */
+ public const ALIASES = [
+ ConsoleCommandEvent::class => self::COMMAND,
+ ConsoleErrorEvent::class => self::ERROR,
+ ConsoleSignalEvent::class => self::SIGNAL,
+ ConsoleTerminateEvent::class => self::TERMINATE,
+ ];
+}
diff --git a/vendor/symfony/console/Cursor.php b/vendor/symfony/console/Cursor.php
new file mode 100644
index 0000000..0c4dafb
--- /dev/null
+++ b/vendor/symfony/console/Cursor.php
@@ -0,0 +1,207 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console;
+
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Pierre du Plessis
+ */
+final class Cursor
+{
+ private $output;
+ private $input;
+
+ /**
+ * @param resource|null $input
+ */
+ public function __construct(OutputInterface $output, $input = null)
+ {
+ $this->output = $output;
+ $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
+ }
+
+ /**
+ * @return $this
+ */
+ public function moveUp(int $lines = 1): self
+ {
+ $this->output->write(sprintf("\x1b[%dA", $lines));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function moveDown(int $lines = 1): self
+ {
+ $this->output->write(sprintf("\x1b[%dB", $lines));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function moveRight(int $columns = 1): self
+ {
+ $this->output->write(sprintf("\x1b[%dC", $columns));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function moveLeft(int $columns = 1): self
+ {
+ $this->output->write(sprintf("\x1b[%dD", $columns));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function moveToColumn(int $column): self
+ {
+ $this->output->write(sprintf("\x1b[%dG", $column));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function moveToPosition(int $column, int $row): self
+ {
+ $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function savePosition(): self
+ {
+ $this->output->write("\x1b7");
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function restorePosition(): self
+ {
+ $this->output->write("\x1b8");
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function hide(): self
+ {
+ $this->output->write("\x1b[?25l");
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function show(): self
+ {
+ $this->output->write("\x1b[?25h\x1b[?0c");
+
+ return $this;
+ }
+
+ /**
+ * Clears all the output from the current line.
+ *
+ * @return $this
+ */
+ public function clearLine(): self
+ {
+ $this->output->write("\x1b[2K");
+
+ return $this;
+ }
+
+ /**
+ * Clears all the output from the current line after the current position.
+ */
+ public function clearLineAfter(): self
+ {
+ $this->output->write("\x1b[K");
+
+ return $this;
+ }
+
+ /**
+ * Clears all the output from the cursors' current position to the end of the screen.
+ *
+ * @return $this
+ */
+ public function clearOutput(): self
+ {
+ $this->output->write("\x1b[0J");
+
+ return $this;
+ }
+
+ /**
+ * Clears the entire screen.
+ *
+ * @return $this
+ */
+ public function clearScreen(): self
+ {
+ $this->output->write("\x1b[2J");
+
+ return $this;
+ }
+
+ /**
+ * Returns the current cursor position as x,y coordinates.
+ */
+ public function getCurrentPosition(): array
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported && \function_exists('proc_open')) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ if (!$isTtySupported) {
+ return [1, 1];
+ }
+
+ $sttyMode = shell_exec('stty -g');
+ shell_exec('stty -icanon -echo');
+
+ @fwrite($this->input, "\033[6n");
+
+ $code = trim(fread($this->input, 1024));
+
+ shell_exec(sprintf('stty %s', $sttyMode));
+
+ sscanf($code, "\033[%d;%dR", $row, $col);
+
+ return [$col, $row];
+ }
+}
diff --git a/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php
new file mode 100644
index 0000000..743e306
--- /dev/null
+++ b/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php
@@ -0,0 +1,148 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\DependencyInjection;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\LazyCommand;
+use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
+use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\TypedReference;
+
+/**
+ * Registers console commands.
+ *
+ * @author Grégoire Pineau
+ */
+class AddConsoleCommandPass implements CompilerPassInterface
+{
+ private $commandLoaderServiceId;
+ private $commandTag;
+ private $noPreloadTag;
+ private $privateTagName;
+
+ public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private')
+ {
+ if (0 < \func_num_args()) {
+ trigger_deprecation('symfony/console', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
+ }
+
+ $this->commandLoaderServiceId = $commandLoaderServiceId;
+ $this->commandTag = $commandTag;
+ $this->noPreloadTag = $noPreloadTag;
+ $this->privateTagName = $privateTagName;
+ }
+
+ public function process(ContainerBuilder $container)
+ {
+ $commandServices = $container->findTaggedServiceIds($this->commandTag, true);
+ $lazyCommandMap = [];
+ $lazyCommandRefs = [];
+ $serviceIds = [];
+
+ foreach ($commandServices as $id => $tags) {
+ $definition = $container->getDefinition($id);
+ $definition->addTag($this->noPreloadTag);
+ $class = $container->getParameterBag()->resolveValue($definition->getClass());
+
+ if (isset($tags[0]['command'])) {
+ $aliases = $tags[0]['command'];
+ } else {
+ if (!$r = $container->getReflectionClass($class)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+ if (!$r->isSubclassOf(Command::class)) {
+ throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
+ }
+ $aliases = $class::getDefaultName();
+ }
+
+ $aliases = explode('|', $aliases ?? '');
+ $commandName = array_shift($aliases);
+
+ if ($isHidden = '' === $commandName) {
+ $commandName = array_shift($aliases);
+ }
+
+ if (null === $commandName) {
+ if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) {
+ $commandId = 'console.command.public_alias.'.$id;
+ $container->setAlias($commandId, $id)->setPublic(true);
+ $id = $commandId;
+ }
+ $serviceIds[] = $id;
+
+ continue;
+ }
+
+ $description = $tags[0]['description'] ?? null;
+
+ unset($tags[0]);
+ $lazyCommandMap[$commandName] = $id;
+ $lazyCommandRefs[$id] = new TypedReference($id, $class);
+
+ foreach ($aliases as $alias) {
+ $lazyCommandMap[$alias] = $id;
+ }
+
+ foreach ($tags as $tag) {
+ if (isset($tag['command'])) {
+ $aliases[] = $tag['command'];
+ $lazyCommandMap[$tag['command']] = $id;
+ }
+
+ $description = $description ?? $tag['description'] ?? null;
+ }
+
+ $definition->addMethodCall('setName', [$commandName]);
+
+ if ($aliases) {
+ $definition->addMethodCall('setAliases', [$aliases]);
+ }
+
+ if ($isHidden) {
+ $definition->addMethodCall('setHidden', [true]);
+ }
+
+ if (!$description) {
+ if (!$r = $container->getReflectionClass($class)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
+ }
+ if (!$r->isSubclassOf(Command::class)) {
+ throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
+ }
+ $description = $class::getDefaultDescription();
+ }
+
+ if ($description) {
+ $definition->addMethodCall('setDescription', [$description]);
+
+ $container->register('.'.$id.'.lazy', LazyCommand::class)
+ ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);
+
+ $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
+ }
+ }
+
+ $container
+ ->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
+ ->setPublic(true)
+ ->addTag($this->noPreloadTag)
+ ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
+
+ $container->setParameter('console.command.ids', $serviceIds);
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php
new file mode 100644
index 0000000..fac01ad
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\CommandNotFoundException;
+
+/**
+ * @author Jean-François Simon
+ *
+ * @internal
+ */
+class ApplicationDescription
+{
+ public const GLOBAL_NAMESPACE = '_global';
+
+ private $application;
+ private $namespace;
+ private $showHidden;
+
+ /**
+ * @var array
+ */
+ private $namespaces;
+
+ /**
+ * @var array
+ */
+ private $commands;
+
+ /**
+ * @var array
+ */
+ private $aliases;
+
+ public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
+ {
+ $this->application = $application;
+ $this->namespace = $namespace;
+ $this->showHidden = $showHidden;
+ }
+
+ public function getNamespaces(): array
+ {
+ if (null === $this->namespaces) {
+ $this->inspectApplication();
+ }
+
+ return $this->namespaces;
+ }
+
+ /**
+ * @return Command[]
+ */
+ public function getCommands(): array
+ {
+ if (null === $this->commands) {
+ $this->inspectApplication();
+ }
+
+ return $this->commands;
+ }
+
+ /**
+ * @throws CommandNotFoundException
+ */
+ public function getCommand(string $name): Command
+ {
+ if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
+ throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
+ }
+
+ return $this->commands[$name] ?? $this->aliases[$name];
+ }
+
+ private function inspectApplication()
+ {
+ $this->commands = [];
+ $this->namespaces = [];
+
+ $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
+ foreach ($this->sortCommands($all) as $namespace => $commands) {
+ $names = [];
+
+ /** @var Command $command */
+ foreach ($commands as $name => $command) {
+ if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
+ continue;
+ }
+
+ if ($command->getName() === $name) {
+ $this->commands[$name] = $command;
+ } else {
+ $this->aliases[$name] = $command;
+ }
+
+ $names[] = $name;
+ }
+
+ $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
+ }
+ }
+
+ private function sortCommands(array $commands): array
+ {
+ $namespacedCommands = [];
+ $globalCommands = [];
+ $sortedCommands = [];
+ foreach ($commands as $name => $command) {
+ $key = $this->application->extractNamespace($name, 1);
+ if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
+ $globalCommands[$name] = $command;
+ } else {
+ $namespacedCommands[$key][$name] = $command;
+ }
+ }
+
+ if ($globalCommands) {
+ ksort($globalCommands);
+ $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
+ }
+
+ if ($namespacedCommands) {
+ ksort($namespacedCommands);
+ foreach ($namespacedCommands as $key => $commandsSet) {
+ ksort($commandsSet);
+ $sortedCommands[$key] = $commandsSet;
+ }
+ }
+
+ return $sortedCommands;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php
new file mode 100644
index 0000000..a364830
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/Descriptor.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Jean-François Simon
+ *
+ * @internal
+ */
+abstract class Descriptor implements DescriptorInterface
+{
+ /**
+ * @var OutputInterface
+ */
+ protected $output;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function describe(OutputInterface $output, object $object, array $options = [])
+ {
+ $this->output = $output;
+
+ switch (true) {
+ case $object instanceof InputArgument:
+ $this->describeInputArgument($object, $options);
+ break;
+ case $object instanceof InputOption:
+ $this->describeInputOption($object, $options);
+ break;
+ case $object instanceof InputDefinition:
+ $this->describeInputDefinition($object, $options);
+ break;
+ case $object instanceof Command:
+ $this->describeCommand($object, $options);
+ break;
+ case $object instanceof Application:
+ $this->describeApplication($object, $options);
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
+ }
+ }
+
+ /**
+ * Writes content to output.
+ */
+ protected function write(string $content, bool $decorated = false)
+ {
+ $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
+ }
+
+ /**
+ * Describes an InputArgument instance.
+ */
+ abstract protected function describeInputArgument(InputArgument $argument, array $options = []);
+
+ /**
+ * Describes an InputOption instance.
+ */
+ abstract protected function describeInputOption(InputOption $option, array $options = []);
+
+ /**
+ * Describes an InputDefinition instance.
+ */
+ abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);
+
+ /**
+ * Describes a Command instance.
+ */
+ abstract protected function describeCommand(Command $command, array $options = []);
+
+ /**
+ * Describes an Application instance.
+ */
+ abstract protected function describeApplication(Application $application, array $options = []);
+}
diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php
new file mode 100644
index 0000000..ebea303
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/DescriptorInterface.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\Console\Descriptor;
+
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Descriptor interface.
+ *
+ * @author Jean-François Simon
+ */
+interface DescriptorInterface
+{
+ public function describe(OutputInterface $output, object $object, array $options = []);
+}
diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php
new file mode 100644
index 0000000..1d28659
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php
@@ -0,0 +1,181 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * JSON descriptor.
+ *
+ * @author Jean-François Simon
+ *
+ * @internal
+ */
+class JsonDescriptor extends Descriptor
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputArgument(InputArgument $argument, array $options = [])
+ {
+ $this->writeData($this->getInputArgumentData($argument), $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputOption(InputOption $option, array $options = [])
+ {
+ $this->writeData($this->getInputOptionData($option), $options);
+ if ($option->isNegatable()) {
+ $this->writeData($this->getInputOptionData($option, true), $options);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+ {
+ $this->writeData($this->getInputDefinitionData($definition), $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeCommand(Command $command, array $options = [])
+ {
+ $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeApplication(Application $application, array $options = [])
+ {
+ $describedNamespace = $options['namespace'] ?? null;
+ $description = new ApplicationDescription($application, $describedNamespace, true);
+ $commands = [];
+
+ foreach ($description->getCommands() as $command) {
+ $commands[] = $this->getCommandData($command, $options['short'] ?? false);
+ }
+
+ $data = [];
+ if ('UNKNOWN' !== $application->getName()) {
+ $data['application']['name'] = $application->getName();
+ if ('UNKNOWN' !== $application->getVersion()) {
+ $data['application']['version'] = $application->getVersion();
+ }
+ }
+
+ $data['commands'] = $commands;
+
+ if ($describedNamespace) {
+ $data['namespace'] = $describedNamespace;
+ } else {
+ $data['namespaces'] = array_values($description->getNamespaces());
+ }
+
+ $this->writeData($data, $options);
+ }
+
+ /**
+ * Writes data as json.
+ */
+ private function writeData(array $data, array $options)
+ {
+ $flags = $options['json_encoding'] ?? 0;
+
+ $this->write(json_encode($data, $flags));
+ }
+
+ private function getInputArgumentData(InputArgument $argument): array
+ {
+ return [
+ 'name' => $argument->getName(),
+ 'is_required' => $argument->isRequired(),
+ 'is_array' => $argument->isArray(),
+ 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
+ 'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
+ ];
+ }
+
+ private function getInputOptionData(InputOption $option, bool $negated = false): array
+ {
+ return $negated ? [
+ 'name' => '--no-'.$option->getName(),
+ 'shortcut' => '',
+ 'accept_value' => false,
+ 'is_value_required' => false,
+ 'is_multiple' => false,
+ 'description' => 'Negate the "--'.$option->getName().'" option',
+ 'default' => false,
+ ] : [
+ 'name' => '--'.$option->getName(),
+ 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
+ 'accept_value' => $option->acceptValue(),
+ 'is_value_required' => $option->isValueRequired(),
+ 'is_multiple' => $option->isArray(),
+ 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
+ 'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
+ ];
+ }
+
+ private function getInputDefinitionData(InputDefinition $definition): array
+ {
+ $inputArguments = [];
+ foreach ($definition->getArguments() as $name => $argument) {
+ $inputArguments[$name] = $this->getInputArgumentData($argument);
+ }
+
+ $inputOptions = [];
+ foreach ($definition->getOptions() as $name => $option) {
+ $inputOptions[$name] = $this->getInputOptionData($option);
+ if ($option->isNegatable()) {
+ $inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
+ }
+ }
+
+ return ['arguments' => $inputArguments, 'options' => $inputOptions];
+ }
+
+ private function getCommandData(Command $command, bool $short = false): array
+ {
+ $data = [
+ 'name' => $command->getName(),
+ 'description' => $command->getDescription(),
+ ];
+
+ if ($short) {
+ $data += [
+ 'usage' => $command->getAliases(),
+ ];
+ } else {
+ $command->mergeApplicationDefinition(false);
+
+ $data += [
+ 'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
+ 'help' => $command->getProcessedHelp(),
+ 'definition' => $this->getInputDefinitionData($command->getDefinition()),
+ ];
+ }
+
+ $data['hidden'] = $command->isHidden();
+
+ return $data;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php
new file mode 100644
index 0000000..21ceca6
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php
@@ -0,0 +1,206 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Helper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Markdown descriptor.
+ *
+ * @author Jean-François Simon
+ *
+ * @internal
+ */
+class MarkdownDescriptor extends Descriptor
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function describe(OutputInterface $output, object $object, array $options = [])
+ {
+ $decorated = $output->isDecorated();
+ $output->setDecorated(false);
+
+ parent::describe($output, $object, $options);
+
+ $output->setDecorated($decorated);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function write(string $content, bool $decorated = true)
+ {
+ parent::write($content, $decorated);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputArgument(InputArgument $argument, array $options = [])
+ {
+ $this->write(
+ '#### `'.($argument->getName() ?: '')."`\n\n"
+ .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
+ .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
+ .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
+ .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputOption(InputOption $option, array $options = [])
+ {
+ $name = '--'.$option->getName();
+ if ($option->isNegatable()) {
+ $name .= '|--no-'.$option->getName();
+ }
+ if ($option->getShortcut()) {
+ $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
+ }
+
+ $this->write(
+ '#### `'.$name.'`'."\n\n"
+ .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
+ .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
+ .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
+ .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
+ .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
+ .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+ {
+ if ($showArguments = \count($definition->getArguments()) > 0) {
+ $this->write('### Arguments');
+ foreach ($definition->getArguments() as $argument) {
+ $this->write("\n\n");
+ if (null !== $describeInputArgument = $this->describeInputArgument($argument)) {
+ $this->write($describeInputArgument);
+ }
+ }
+ }
+
+ if (\count($definition->getOptions()) > 0) {
+ if ($showArguments) {
+ $this->write("\n\n");
+ }
+
+ $this->write('### Options');
+ foreach ($definition->getOptions() as $option) {
+ $this->write("\n\n");
+ if (null !== $describeInputOption = $this->describeInputOption($option)) {
+ $this->write($describeInputOption);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeCommand(Command $command, array $options = [])
+ {
+ if ($options['short'] ?? false) {
+ $this->write(
+ '`'.$command->getName()."`\n"
+ .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
+ .($command->getDescription() ? $command->getDescription()."\n\n" : '')
+ .'### Usage'."\n\n"
+ .array_reduce($command->getAliases(), function ($carry, $usage) {
+ return $carry.'* `'.$usage.'`'."\n";
+ })
+ );
+
+ return;
+ }
+
+ $command->mergeApplicationDefinition(false);
+
+ $this->write(
+ '`'.$command->getName()."`\n"
+ .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
+ .($command->getDescription() ? $command->getDescription()."\n\n" : '')
+ .'### Usage'."\n\n"
+ .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
+ return $carry.'* `'.$usage.'`'."\n";
+ })
+ );
+
+ if ($help = $command->getProcessedHelp()) {
+ $this->write("\n");
+ $this->write($help);
+ }
+
+ $definition = $command->getDefinition();
+ if ($definition->getOptions() || $definition->getArguments()) {
+ $this->write("\n\n");
+ $this->describeInputDefinition($definition);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeApplication(Application $application, array $options = [])
+ {
+ $describedNamespace = $options['namespace'] ?? null;
+ $description = new ApplicationDescription($application, $describedNamespace);
+ $title = $this->getApplicationTitle($application);
+
+ $this->write($title."\n".str_repeat('=', Helper::width($title)));
+
+ foreach ($description->getNamespaces() as $namespace) {
+ if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
+ $this->write("\n\n");
+ $this->write('**'.$namespace['id'].':**');
+ }
+
+ $this->write("\n\n");
+ $this->write(implode("\n", array_map(function ($commandName) use ($description) {
+ return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
+ }, $namespace['commands'])));
+ }
+
+ foreach ($description->getCommands() as $command) {
+ $this->write("\n\n");
+ if (null !== $describeCommand = $this->describeCommand($command, $options)) {
+ $this->write($describeCommand);
+ }
+ }
+ }
+
+ private function getApplicationTitle(Application $application): string
+ {
+ if ('UNKNOWN' !== $application->getName()) {
+ if ('UNKNOWN' !== $application->getVersion()) {
+ return sprintf('%s %s', $application->getName(), $application->getVersion());
+ }
+
+ return $application->getName();
+ }
+
+ return 'Console Tool';
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php
new file mode 100644
index 0000000..fbb140a
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/TextDescriptor.php
@@ -0,0 +1,341 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Formatter\OutputFormatter;
+use Symfony\Component\Console\Helper\Helper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * Text descriptor.
+ *
+ * @author Jean-François Simon
+ *
+ * @internal
+ */
+class TextDescriptor extends Descriptor
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputArgument(InputArgument $argument, array $options = [])
+ {
+ if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
+ $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
+ $spacingWidth = $totalWidth - \strlen($argument->getName());
+
+ $this->writeText(sprintf(' %s %s%s%s',
+ $argument->getName(),
+ str_repeat(' ', $spacingWidth),
+ // + 4 = 2 spaces before , 2 spaces after
+ preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
+ $default
+ ), $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputOption(InputOption $option, array $options = [])
+ {
+ if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
+ $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault()));
+ } else {
+ $default = '';
+ }
+
+ $value = '';
+ if ($option->acceptValue()) {
+ $value = '='.strtoupper($option->getName());
+
+ if ($option->isValueOptional()) {
+ $value = '['.$value.']';
+ }
+ }
+
+ $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
+ $synopsis = sprintf('%s%s',
+ $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
+ sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
+ );
+
+ $spacingWidth = $totalWidth - Helper::width($synopsis);
+
+ $this->writeText(sprintf(' %s %s%s%s%s',
+ $synopsis,
+ str_repeat(' ', $spacingWidth),
+ // + 4 = 2 spaces before , 2 spaces after
+ preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
+ $default,
+ $option->isArray() ? ' (multiple values allowed)' : ''
+ ), $options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+ {
+ $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
+ foreach ($definition->getArguments() as $argument) {
+ $totalWidth = max($totalWidth, Helper::width($argument->getName()));
+ }
+
+ if ($definition->getArguments()) {
+ $this->writeText('Arguments:', $options);
+ $this->writeText("\n");
+ foreach ($definition->getArguments() as $argument) {
+ $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
+ $this->writeText("\n");
+ }
+ }
+
+ if ($definition->getArguments() && $definition->getOptions()) {
+ $this->writeText("\n");
+ }
+
+ if ($definition->getOptions()) {
+ $laterOptions = [];
+
+ $this->writeText('Options:', $options);
+ foreach ($definition->getOptions() as $option) {
+ if (\strlen($option->getShortcut() ?? '') > 1) {
+ $laterOptions[] = $option;
+ continue;
+ }
+ $this->writeText("\n");
+ $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+ }
+ foreach ($laterOptions as $option) {
+ $this->writeText("\n");
+ $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeCommand(Command $command, array $options = [])
+ {
+ $command->mergeApplicationDefinition(false);
+
+ if ($description = $command->getDescription()) {
+ $this->writeText('Description:', $options);
+ $this->writeText("\n");
+ $this->writeText(' '.$description);
+ $this->writeText("\n\n");
+ }
+
+ $this->writeText('Usage:', $options);
+ foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
+ $this->writeText("\n");
+ $this->writeText(' '.OutputFormatter::escape($usage), $options);
+ }
+ $this->writeText("\n");
+
+ $definition = $command->getDefinition();
+ if ($definition->getOptions() || $definition->getArguments()) {
+ $this->writeText("\n");
+ $this->describeInputDefinition($definition, $options);
+ $this->writeText("\n");
+ }
+
+ $help = $command->getProcessedHelp();
+ if ($help && $help !== $description) {
+ $this->writeText("\n");
+ $this->writeText('Help:', $options);
+ $this->writeText("\n");
+ $this->writeText(' '.str_replace("\n", "\n ", $help), $options);
+ $this->writeText("\n");
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeApplication(Application $application, array $options = [])
+ {
+ $describedNamespace = $options['namespace'] ?? null;
+ $description = new ApplicationDescription($application, $describedNamespace);
+
+ if (isset($options['raw_text']) && $options['raw_text']) {
+ $width = $this->getColumnWidth($description->getCommands());
+
+ foreach ($description->getCommands() as $command) {
+ $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
+ $this->writeText("\n");
+ }
+ } else {
+ if ('' != $help = $application->getHelp()) {
+ $this->writeText("$help\n\n", $options);
+ }
+
+ $this->writeText("Usage:\n", $options);
+ $this->writeText(" command [options] [arguments]\n\n", $options);
+
+ $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
+
+ $this->writeText("\n");
+ $this->writeText("\n");
+
+ $commands = $description->getCommands();
+ $namespaces = $description->getNamespaces();
+ if ($describedNamespace && $namespaces) {
+ // make sure all alias commands are included when describing a specific namespace
+ $describedNamespaceInfo = reset($namespaces);
+ foreach ($describedNamespaceInfo['commands'] as $name) {
+ $commands[$name] = $description->getCommand($name);
+ }
+ }
+
+ // calculate max. width based on available commands per namespace
+ $width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
+ return array_intersect($namespace['commands'], array_keys($commands));
+ }, array_values($namespaces)))));
+
+ if ($describedNamespace) {
+ $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options);
+ } else {
+ $this->writeText('Available commands:', $options);
+ }
+
+ foreach ($namespaces as $namespace) {
+ $namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
+ return isset($commands[$name]);
+ });
+
+ if (!$namespace['commands']) {
+ continue;
+ }
+
+ if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
+ $this->writeText("\n");
+ $this->writeText(' '.$namespace['id'].'', $options);
+ }
+
+ foreach ($namespace['commands'] as $name) {
+ $this->writeText("\n");
+ $spacingWidth = $width - Helper::width($name);
+ $command = $commands[$name];
+ $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
+ $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
+ }
+ }
+
+ $this->writeText("\n");
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ private function writeText(string $content, array $options = [])
+ {
+ $this->write(
+ isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
+ isset($options['raw_output']) ? !$options['raw_output'] : true
+ );
+ }
+
+ /**
+ * Formats command aliases to show them in the command description.
+ */
+ private function getCommandAliasesText(Command $command): string
+ {
+ $text = '';
+ $aliases = $command->getAliases();
+
+ if ($aliases) {
+ $text = '['.implode('|', $aliases).'] ';
+ }
+
+ return $text;
+ }
+
+ /**
+ * Formats input option/argument default value.
+ *
+ * @param mixed $default
+ */
+ private function formatDefaultValue($default): string
+ {
+ if (\INF === $default) {
+ return 'INF';
+ }
+
+ if (\is_string($default)) {
+ $default = OutputFormatter::escape($default);
+ } elseif (\is_array($default)) {
+ foreach ($default as $key => $value) {
+ if (\is_string($value)) {
+ $default[$key] = OutputFormatter::escape($value);
+ }
+ }
+ }
+
+ return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
+ }
+
+ /**
+ * @param array $commands
+ */
+ private function getColumnWidth(array $commands): int
+ {
+ $widths = [];
+
+ foreach ($commands as $command) {
+ if ($command instanceof Command) {
+ $widths[] = Helper::width($command->getName());
+ foreach ($command->getAliases() as $alias) {
+ $widths[] = Helper::width($alias);
+ }
+ } else {
+ $widths[] = Helper::width($command);
+ }
+ }
+
+ return $widths ? max($widths) + 2 : 0;
+ }
+
+ /**
+ * @param InputOption[] $options
+ */
+ private function calculateTotalWidthForOptions(array $options): int
+ {
+ $totalWidth = 0;
+ foreach ($options as $option) {
+ // "-" + shortcut + ", --" + name
+ $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName());
+ if ($option->isNegatable()) {
+ $nameLength += 6 + Helper::width($option->getName()); // |--no- + name
+ } elseif ($option->acceptValue()) {
+ $valueLength = 1 + Helper::width($option->getName()); // = + value
+ $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
+
+ $nameLength += $valueLength;
+ }
+ $totalWidth = max($totalWidth, $nameLength);
+ }
+
+ return $totalWidth;
+ }
+}
diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php
new file mode 100644
index 0000000..4f7cd8b
--- /dev/null
+++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php
@@ -0,0 +1,247 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Descriptor;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+
+/**
+ * XML descriptor.
+ *
+ * @author Jean-François Simon
+ *
+ * @internal
+ */
+class XmlDescriptor extends Descriptor
+{
+ public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->appendChild($definitionXML = $dom->createElement('definition'));
+
+ $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
+ foreach ($definition->getArguments() as $argument) {
+ $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
+ }
+
+ $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
+ foreach ($definition->getOptions() as $option) {
+ $this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
+ }
+
+ return $dom;
+ }
+
+ public function getCommandDocument(Command $command, bool $short = false): \DOMDocument
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->appendChild($commandXML = $dom->createElement('command'));
+
+ $commandXML->setAttribute('id', $command->getName());
+ $commandXML->setAttribute('name', $command->getName());
+ $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);
+
+ $commandXML->appendChild($usagesXML = $dom->createElement('usages'));
+
+ $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
+
+ if ($short) {
+ foreach ($command->getAliases() as $usage) {
+ $usagesXML->appendChild($dom->createElement('usage', $usage));
+ }
+ } else {
+ $command->mergeApplicationDefinition(false);
+
+ foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
+ $usagesXML->appendChild($dom->createElement('usage', $usage));
+ }
+
+ $commandXML->appendChild($helpXML = $dom->createElement('help'));
+ $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
+
+ $definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
+ $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
+ }
+
+ return $dom;
+ }
+
+ public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+ $dom->appendChild($rootXml = $dom->createElement('symfony'));
+
+ if ('UNKNOWN' !== $application->getName()) {
+ $rootXml->setAttribute('name', $application->getName());
+ if ('UNKNOWN' !== $application->getVersion()) {
+ $rootXml->setAttribute('version', $application->getVersion());
+ }
+ }
+
+ $rootXml->appendChild($commandsXML = $dom->createElement('commands'));
+
+ $description = new ApplicationDescription($application, $namespace, true);
+
+ if ($namespace) {
+ $commandsXML->setAttribute('namespace', $namespace);
+ }
+
+ foreach ($description->getCommands() as $command) {
+ $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short));
+ }
+
+ if (!$namespace) {
+ $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
+
+ foreach ($description->getNamespaces() as $namespaceDescription) {
+ $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
+ $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
+
+ foreach ($namespaceDescription['commands'] as $name) {
+ $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
+ $commandXML->appendChild($dom->createTextNode($name));
+ }
+ }
+ }
+
+ return $dom;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputArgument(InputArgument $argument, array $options = [])
+ {
+ $this->writeDocument($this->getInputArgumentDocument($argument));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputOption(InputOption $option, array $options = [])
+ {
+ $this->writeDocument($this->getInputOptionDocument($option));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+ {
+ $this->writeDocument($this->getInputDefinitionDocument($definition));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeCommand(Command $command, array $options = [])
+ {
+ $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function describeApplication(Application $application, array $options = [])
+ {
+ $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
+ }
+
+ /**
+ * Appends document children to parent node.
+ */
+ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
+ {
+ foreach ($importedParent->childNodes as $childNode) {
+ $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
+ }
+ }
+
+ /**
+ * Writes DOM document.
+ */
+ private function writeDocument(\DOMDocument $dom)
+ {
+ $dom->formatOutput = true;
+ $this->write($dom->saveXML());
+ }
+
+ private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+
+ $dom->appendChild($objectXML = $dom->createElement('argument'));
+ $objectXML->setAttribute('name', $argument->getName());
+ $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
+ $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
+ $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
+
+ $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
+ $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
+ foreach ($defaults as $default) {
+ $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
+ $defaultXML->appendChild($dom->createTextNode($default));
+ }
+
+ return $dom;
+ }
+
+ private function getInputOptionDocument(InputOption $option): \DOMDocument
+ {
+ $dom = new \DOMDocument('1.0', 'UTF-8');
+
+ $dom->appendChild($objectXML = $dom->createElement('option'));
+ $objectXML->setAttribute('name', '--'.$option->getName());
+ $pos = strpos($option->getShortcut() ?? '', '|');
+ if (false !== $pos) {
+ $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
+ $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
+ } else {
+ $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
+ }
+ $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
+ $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
+ $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
+ $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
+
+ if ($option->acceptValue()) {
+ $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
+ $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
+
+ if (!empty($defaults)) {
+ foreach ($defaults as $default) {
+ $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
+ $defaultXML->appendChild($dom->createTextNode($default));
+ }
+ }
+ }
+
+ if ($option->isNegatable()) {
+ $dom->appendChild($objectXML = $dom->createElement('option'));
+ $objectXML->setAttribute('name', '--no-'.$option->getName());
+ $objectXML->setAttribute('shortcut', '');
+ $objectXML->setAttribute('accept_value', 0);
+ $objectXML->setAttribute('is_value_required', 0);
+ $objectXML->setAttribute('is_multiple', 0);
+ $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
+ $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
+ }
+
+ return $dom;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php
new file mode 100644
index 0000000..08bd18f
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Event;
+
+/**
+ * Allows to do things before the command is executed, like skipping the command or changing the input.
+ *
+ * @author Fabien Potencier
+ */
+final class ConsoleCommandEvent extends ConsoleEvent
+{
+ /**
+ * The return code for skipped commands, this will also be passed into the terminate event.
+ */
+ public const RETURN_CODE_DISABLED = 113;
+
+ /**
+ * Indicates if the command should be run or skipped.
+ */
+ private $commandShouldRun = true;
+
+ /**
+ * Disables the command, so it won't be run.
+ */
+ public function disableCommand(): bool
+ {
+ return $this->commandShouldRun = false;
+ }
+
+ public function enableCommand(): bool
+ {
+ return $this->commandShouldRun = true;
+ }
+
+ /**
+ * Returns true if the command is runnable, false otherwise.
+ */
+ public function commandShouldRun(): bool
+ {
+ return $this->commandShouldRun;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleErrorEvent.php b/vendor/symfony/console/Event/ConsoleErrorEvent.php
new file mode 100644
index 0000000..57d9b38
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleErrorEvent.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Event;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Allows to handle throwables thrown while running a command.
+ *
+ * @author Wouter de Jong
+ */
+final class ConsoleErrorEvent extends ConsoleEvent
+{
+ private $error;
+ private $exitCode;
+
+ public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
+ {
+ parent::__construct($command, $input, $output);
+
+ $this->error = $error;
+ }
+
+ public function getError(): \Throwable
+ {
+ return $this->error;
+ }
+
+ public function setError(\Throwable $error): void
+ {
+ $this->error = $error;
+ }
+
+ public function setExitCode(int $exitCode): void
+ {
+ $this->exitCode = $exitCode;
+
+ $r = new \ReflectionProperty($this->error, 'code');
+ $r->setAccessible(true);
+ $r->setValue($this->error, $this->exitCode);
+ }
+
+ public function getExitCode(): int
+ {
+ return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php
new file mode 100644
index 0000000..be7937d
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleEvent.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Event;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * Allows to inspect input and output of a command.
+ *
+ * @author Francesco Levorato
+ */
+class ConsoleEvent extends Event
+{
+ protected $command;
+
+ private $input;
+ private $output;
+
+ public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
+ {
+ $this->command = $command;
+ $this->input = $input;
+ $this->output = $output;
+ }
+
+ /**
+ * Gets the command that is executed.
+ *
+ * @return Command|null
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+
+ /**
+ * Gets the input instance.
+ *
+ * @return InputInterface
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Gets the output instance.
+ *
+ * @return OutputInterface
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleSignalEvent.php b/vendor/symfony/console/Event/ConsoleSignalEvent.php
new file mode 100644
index 0000000..ef13ed2
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleSignalEvent.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Event;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author marie
+ */
+final class ConsoleSignalEvent extends ConsoleEvent
+{
+ private $handlingSignal;
+
+ public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
+ {
+ parent::__construct($command, $input, $output);
+ $this->handlingSignal = $handlingSignal;
+ }
+
+ public function getHandlingSignal(): int
+ {
+ return $this->handlingSignal;
+ }
+}
diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php
new file mode 100644
index 0000000..190038d
--- /dev/null
+++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Event;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Allows to manipulate the exit code of a command after its execution.
+ *
+ * @author Francesco Levorato
+ */
+final class ConsoleTerminateEvent extends ConsoleEvent
+{
+ private $exitCode;
+
+ public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode)
+ {
+ parent::__construct($command, $input, $output);
+
+ $this->setExitCode($exitCode);
+ }
+
+ public function setExitCode(int $exitCode): void
+ {
+ $this->exitCode = $exitCode;
+ }
+
+ public function getExitCode(): int
+ {
+ return $this->exitCode;
+ }
+}
diff --git a/vendor/symfony/console/EventListener/ErrorListener.php b/vendor/symfony/console/EventListener/ErrorListener.php
new file mode 100644
index 0000000..897d985
--- /dev/null
+++ b/vendor/symfony/console/EventListener/ErrorListener.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\EventListener;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\ConsoleEvents;
+use Symfony\Component\Console\Event\ConsoleErrorEvent;
+use Symfony\Component\Console\Event\ConsoleEvent;
+use Symfony\Component\Console\Event\ConsoleTerminateEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @author James Halsall
+ * @author Robin Chalas
+ */
+class ErrorListener implements EventSubscriberInterface
+{
+ private $logger;
+
+ public function __construct(LoggerInterface $logger = null)
+ {
+ $this->logger = $logger;
+ }
+
+ public function onConsoleError(ConsoleErrorEvent $event)
+ {
+ if (null === $this->logger) {
+ return;
+ }
+
+ $error = $event->getError();
+
+ if (!$inputString = $this->getInputString($event)) {
+ $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
+
+ return;
+ }
+
+ $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
+ }
+
+ public function onConsoleTerminate(ConsoleTerminateEvent $event)
+ {
+ if (null === $this->logger) {
+ return;
+ }
+
+ $exitCode = $event->getExitCode();
+
+ if (0 === $exitCode) {
+ return;
+ }
+
+ if (!$inputString = $this->getInputString($event)) {
+ $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
+
+ return;
+ }
+
+ $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ ConsoleEvents::ERROR => ['onConsoleError', -128],
+ ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
+ ];
+ }
+
+ private static function getInputString(ConsoleEvent $event): ?string
+ {
+ $commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
+ $input = $event->getInput();
+
+ if (method_exists($input, '__toString')) {
+ if ($commandName) {
+ return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
+ }
+
+ return (string) $input;
+ }
+
+ return $commandName;
+ }
+}
diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php
new file mode 100644
index 0000000..910ae19
--- /dev/null
+++ b/vendor/symfony/console/Exception/CommandNotFoundException.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * Represents an incorrect command name typed in the console.
+ *
+ * @author Jérôme Tamarelle
+ */
+class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
+{
+ private $alternatives;
+
+ /**
+ * @param string $message Exception message to throw
+ * @param string[] $alternatives List of similar defined names
+ * @param int $code Exception code
+ * @param \Throwable|null $previous Previous exception used for the exception chaining
+ */
+ public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+
+ $this->alternatives = $alternatives;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAlternatives()
+ {
+ return $this->alternatives;
+ }
+}
diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..1624e13
--- /dev/null
+++ b/vendor/symfony/console/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * ExceptionInterface.
+ *
+ * @author Jérôme Tamarelle
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..07cc0b6
--- /dev/null
+++ b/vendor/symfony/console/Exception/InvalidArgumentException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * @author Jérôme Tamarelle
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php
new file mode 100644
index 0000000..b2eec61
--- /dev/null
+++ b/vendor/symfony/console/Exception/InvalidOptionException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * Represents an incorrect option name typed in the console.
+ *
+ * @author Jérôme Tamarelle
+ */
+class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php
new file mode 100644
index 0000000..fc37b8d
--- /dev/null
+++ b/vendor/symfony/console/Exception/LogicException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * @author Jérôme Tamarelle
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Exception/MissingInputException.php b/vendor/symfony/console/Exception/MissingInputException.php
new file mode 100644
index 0000000..04f02ad
--- /dev/null
+++ b/vendor/symfony/console/Exception/MissingInputException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * Represents failure to read input from stdin.
+ *
+ * @author Gabriel Ostrolucký
+ */
+class MissingInputException extends RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Exception/NamespaceNotFoundException.php b/vendor/symfony/console/Exception/NamespaceNotFoundException.php
new file mode 100644
index 0000000..dd16e45
--- /dev/null
+++ b/vendor/symfony/console/Exception/NamespaceNotFoundException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * Represents an incorrect namespace typed in the console.
+ *
+ * @author Pierre du Plessis
+ */
+class NamespaceNotFoundException extends CommandNotFoundException
+{
+}
diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php
new file mode 100644
index 0000000..51d7d80
--- /dev/null
+++ b/vendor/symfony/console/Exception/RuntimeException.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Exception;
+
+/**
+ * @author Jérôme Tamarelle
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/console/Formatter/NullOutputFormatter.php b/vendor/symfony/console/Formatter/NullOutputFormatter.php
new file mode 100644
index 0000000..d770e14
--- /dev/null
+++ b/vendor/symfony/console/Formatter/NullOutputFormatter.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Console\Formatter;
+
+/**
+ * @author Tien Xuan Vo