+ * @author Nicolas Grekas
+ */
+class FlockStore implements StoreInterface
+{
+ private $lockPath;
+
+ /**
+ * @param string|null $lockPath the directory to store the lock, defaults to the system's temporary directory
+ *
+ * @throws LockStorageException If the lock directory doesn’t exist or is not writable
+ */
+ public function __construct($lockPath = null)
+ {
+ if (null === $lockPath) {
+ $lockPath = sys_get_temp_dir();
+ }
+ if (!is_dir($lockPath) || !is_writable($lockPath)) {
+ throw new InvalidArgumentException(sprintf('The directory "%s" is not writable.', $lockPath));
+ }
+
+ $this->lockPath = $lockPath;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(Key $key)
+ {
+ $this->lock($key, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function waitAndSave(Key $key)
+ {
+ $this->lock($key, true);
+ }
+
+ private function lock(Key $key, $blocking)
+ {
+ // The lock is maybe already acquired.
+ if ($key->hasState(__CLASS__)) {
+ return;
+ }
+
+ $fileName = sprintf('%s/sf.%s.%s.lock',
+ $this->lockPath,
+ preg_replace('/[^a-z0-9\._-]+/i', '-', $key),
+ strtr(substr(base64_encode(hash('sha256', $key, true)), 0, 7), '/', '_')
+ );
+
+ // Silence error reporting
+ set_error_handler(function () {
+ });
+ if (!$handle = fopen($fileName, 'r')) {
+ if ($handle = fopen($fileName, 'x')) {
+ chmod($fileName, 0444);
+ } elseif (!$handle = fopen($fileName, 'r')) {
+ usleep(100); // Give some time for chmod() to complete
+ $handle = fopen($fileName, 'r');
+ }
+ }
+ restore_error_handler();
+
+ if (!$handle) {
+ $error = error_get_last();
+ throw new LockStorageException($error['message'], 0, null);
+ }
+
+ // On Windows, even if PHP doc says the contrary, LOCK_NB works, see
+ // https://bugs.php.net/54129
+ if (!flock($handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
+ fclose($handle);
+ throw new LockConflictedException();
+ }
+
+ $key->setState(__CLASS__, $handle);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function putOffExpiration(Key $key, $ttl)
+ {
+ // do nothing, the flock locks forever.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(Key $key)
+ {
+ // The lock is maybe not acquired.
+ if (!$key->hasState(__CLASS__)) {
+ return;
+ }
+
+ $handle = $key->getState(__CLASS__);
+
+ flock($handle, LOCK_UN | LOCK_NB);
+ fclose($handle);
+
+ $key->removeState(__CLASS__);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exists(Key $key)
+ {
+ return $key->hasState(__CLASS__);
+ }
+}
diff --git a/vendor/symfony/lock/Store/MemcachedStore.php b/vendor/symfony/lock/Store/MemcachedStore.php
new file mode 100644
index 00000000..8e9db10c
--- /dev/null
+++ b/vendor/symfony/lock/Store/MemcachedStore.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\Lock\Store;
+
+use Symfony\Component\Lock\Exception\InvalidArgumentException;
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Exception\LockExpiredException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * MemcachedStore is a StoreInterface implementation using Memcached as store engine.
+ *
+ * @author Jérémy Derussé
+ */
+class MemcachedStore implements StoreInterface
+{
+ private $memcached;
+ private $initialTtl;
+ /** @var bool */
+ private $useExtendedReturn;
+
+ public static function isSupported()
+ {
+ return extension_loaded('memcached');
+ }
+
+ /**
+ * @param \Memcached $memcached
+ * @param int $initialTtl the expiration delay of locks in seconds
+ */
+ public function __construct(\Memcached $memcached, $initialTtl = 300)
+ {
+ if (!static::isSupported()) {
+ throw new InvalidArgumentException('Memcached extension is required');
+ }
+
+ if ($initialTtl < 1) {
+ throw new InvalidArgumentException(sprintf('%s() expects a strictly positive TTL. Got %d.', __METHOD__, $initialTtl));
+ }
+
+ $this->memcached = $memcached;
+ $this->initialTtl = $initialTtl;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(Key $key)
+ {
+ $token = $this->getToken($key);
+ $key->reduceLifetime($this->initialTtl);
+ if (!$this->memcached->add((string) $key, $token, (int) ceil($this->initialTtl))) {
+ // the lock is already acquired. It could be us. Let's try to put off.
+ $this->putOffExpiration($key, $this->initialTtl);
+ }
+
+ if ($key->isExpired()) {
+ throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key));
+ }
+ }
+
+ public function waitAndSave(Key $key)
+ {
+ throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', get_class($this)));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function putOffExpiration(Key $key, $ttl)
+ {
+ if ($ttl < 1) {
+ throw new InvalidArgumentException(sprintf('%s() expects a TTL greater or equals to 1. Got %s.', __METHOD__, $ttl));
+ }
+
+ // Interface defines a float value but Store required an integer.
+ $ttl = (int) ceil($ttl);
+
+ $token = $this->getToken($key);
+
+ list($value, $cas) = $this->getValueAndCas($key);
+
+ $key->reduceLifetime($ttl);
+ // Could happens when we ask a putOff after a timeout but in luck nobody steal the lock
+ if (\Memcached::RES_NOTFOUND === $this->memcached->getResultCode()) {
+ if ($this->memcached->add((string) $key, $token, $ttl)) {
+ return;
+ }
+
+ // no luck, with concurrency, someone else acquire the lock
+ throw new LockConflictedException();
+ }
+
+ // Someone else steal the lock
+ if ($value !== $token) {
+ throw new LockConflictedException();
+ }
+
+ if (!$this->memcached->cas($cas, (string) $key, $token, $ttl)) {
+ throw new LockConflictedException();
+ }
+
+ if ($key->isExpired()) {
+ throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(Key $key)
+ {
+ $token = $this->getToken($key);
+
+ list($value, $cas) = $this->getValueAndCas($key);
+
+ if ($value !== $token) {
+ // we are not the owner of the lock. Nothing to do.
+ return;
+ }
+
+ // To avoid concurrency in deletion, the trick is to extends the TTL then deleting the key
+ if (!$this->memcached->cas($cas, (string) $key, $token, 2)) {
+ // Someone steal our lock. It does not belongs to us anymore. Nothing to do.
+ return;
+ }
+
+ // Now, we are the owner of the lock for 2 more seconds, we can delete it.
+ $this->memcached->delete((string) $key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exists(Key $key)
+ {
+ return $this->memcached->get((string) $key) === $this->getToken($key);
+ }
+
+ /**
+ * Retrieve an unique token for the given key.
+ *
+ * @param Key $key
+ *
+ * @return string
+ */
+ private function getToken(Key $key)
+ {
+ if (!$key->hasState(__CLASS__)) {
+ $token = base64_encode(random_bytes(32));
+ $key->setState(__CLASS__, $token);
+ }
+
+ return $key->getState(__CLASS__);
+ }
+
+ private function getValueAndCas(Key $key)
+ {
+ if (null === $this->useExtendedReturn) {
+ $this->useExtendedReturn = version_compare(phpversion('memcached'), '2.9.9', '>');
+ }
+
+ if ($this->useExtendedReturn) {
+ $extendedReturn = $this->memcached->get((string) $key, null, \Memcached::GET_EXTENDED);
+ if (\Memcached::GET_ERROR_RETURN_VALUE === $extendedReturn) {
+ return array($extendedReturn, 0.0);
+ }
+
+ return array($extendedReturn['value'], $extendedReturn['cas']);
+ }
+
+ $cas = 0.0;
+ $value = $this->memcached->get((string) $key, null, $cas);
+
+ return array($value, $cas);
+ }
+}
diff --git a/vendor/symfony/lock/Store/RedisStore.php b/vendor/symfony/lock/Store/RedisStore.php
new file mode 100644
index 00000000..4d057337
--- /dev/null
+++ b/vendor/symfony/lock/Store/RedisStore.php
@@ -0,0 +1,166 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Store;
+
+use Symfony\Component\Cache\Traits\RedisProxy;
+use Symfony\Component\Lock\Exception\InvalidArgumentException;
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Exception\LockExpiredException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * RedisStore is a StoreInterface implementation using Redis as store engine.
+ *
+ * @author Jérémy Derussé
+ */
+class RedisStore implements StoreInterface
+{
+ private $redis;
+ private $initialTtl;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
+ * @param float $initialTtl the expiration delay of locks in seconds
+ */
+ public function __construct($redisClient, $initialTtl = 300.0)
+ {
+ if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
+ throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
+ }
+
+ if ($initialTtl <= 0) {
+ throw new InvalidArgumentException(sprintf('%s() expects a strictly positive TTL. Got %d.', __METHOD__, $initialTtl));
+ }
+
+ $this->redis = $redisClient;
+ $this->initialTtl = $initialTtl;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(Key $key)
+ {
+ $script = '
+ if redis.call("GET", KEYS[1]) == ARGV[1] then
+ return redis.call("PEXPIRE", KEYS[1], ARGV[2])
+ else
+ return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
+ end
+ ';
+
+ $key->reduceLifetime($this->initialTtl);
+ if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($this->initialTtl * 1000)))) {
+ throw new LockConflictedException();
+ }
+
+ if ($key->isExpired()) {
+ throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key));
+ }
+ }
+
+ public function waitAndSave(Key $key)
+ {
+ throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', get_class($this)));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function putOffExpiration(Key $key, $ttl)
+ {
+ $script = '
+ if redis.call("GET", KEYS[1]) == ARGV[1] then
+ return redis.call("PEXPIRE", KEYS[1], ARGV[2])
+ else
+ return 0
+ end
+ ';
+
+ $key->reduceLifetime($ttl);
+ if (!$this->evaluate($script, (string) $key, array($this->getToken($key), (int) ceil($ttl * 1000)))) {
+ throw new LockConflictedException();
+ }
+
+ if ($key->isExpired()) {
+ throw new LockExpiredException(sprintf('Failed to put off the expiration of the "%s" lock within the specified time.', $key));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(Key $key)
+ {
+ $script = '
+ if redis.call("GET", KEYS[1]) == ARGV[1] then
+ return redis.call("DEL", KEYS[1])
+ else
+ return 0
+ end
+ ';
+
+ $this->evaluate($script, (string) $key, array($this->getToken($key)));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exists(Key $key)
+ {
+ return $this->redis->get((string) $key) === $this->getToken($key);
+ }
+
+ /**
+ * Evaluates a script in the corresponding redis client.
+ *
+ * @param string $script
+ * @param string $resource
+ * @param array $args
+ *
+ * @return mixed
+ */
+ private function evaluate($script, $resource, array $args)
+ {
+ if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster || $this->redis instanceof RedisProxy) {
+ return $this->redis->eval($script, array_merge(array($resource), $args), 1);
+ }
+
+ if ($this->redis instanceof \RedisArray) {
+ return $this->redis->_instance($this->redis->_target($resource))->eval($script, array_merge(array($resource), $args), 1);
+ }
+
+ if ($this->redis instanceof \Predis\Client) {
+ return call_user_func_array(array($this->redis, 'eval'), array_merge(array($script, 1, $resource), $args));
+ }
+
+ throw new InvalidArgumentException(sprintf('%s() expects been initialized with a Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($this->redis) ? get_class($this->redis) : gettype($this->redis)));
+ }
+
+ /**
+ * Retrieves an unique token for the given key.
+ *
+ * @param Key $key
+ *
+ * @return string
+ */
+ private function getToken(Key $key)
+ {
+ if (!$key->hasState(__CLASS__)) {
+ $token = base64_encode(random_bytes(32));
+ $key->setState(__CLASS__, $token);
+ }
+
+ return $key->getState(__CLASS__);
+ }
+}
diff --git a/vendor/symfony/lock/Store/RetryTillSaveStore.php b/vendor/symfony/lock/Store/RetryTillSaveStore.php
new file mode 100644
index 00000000..dfc3b266
--- /dev/null
+++ b/vendor/symfony/lock/Store/RetryTillSaveStore.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Store;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * RetryTillSaveStore is a StoreInterface implementation which decorate a non blocking StoreInterface to provide a
+ * blocking storage.
+ *
+ * @author Jérémy Derussé
+ */
+class RetryTillSaveStore implements StoreInterface, LoggerAwareInterface
+{
+ use LoggerAwareTrait;
+
+ private $decorated;
+ private $retrySleep;
+ private $retryCount;
+
+ /**
+ * @param StoreInterface $decorated The decorated StoreInterface
+ * @param int $retrySleep Duration in ms between 2 retry
+ * @param int $retryCount Maximum amount of retry
+ */
+ public function __construct(StoreInterface $decorated, $retrySleep = 100, $retryCount = PHP_INT_MAX)
+ {
+ $this->decorated = $decorated;
+ $this->retrySleep = $retrySleep;
+ $this->retryCount = $retryCount;
+
+ $this->logger = new NullLogger();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(Key $key)
+ {
+ $this->decorated->save($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function waitAndSave(Key $key)
+ {
+ $retry = 0;
+ $sleepRandomness = (int) ($this->retrySleep / 10);
+ do {
+ try {
+ $this->decorated->save($key);
+
+ return;
+ } catch (LockConflictedException $e) {
+ usleep(($this->retrySleep + random_int(-$sleepRandomness, $sleepRandomness)) * 1000);
+ }
+ } while (++$retry < $this->retryCount);
+
+ $this->logger->warning('Failed to store the "{resource}" lock. Abort after {retry} retry.', array('resource' => $key, 'retry' => $retry));
+
+ throw new LockConflictedException();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function putOffExpiration(Key $key, $ttl)
+ {
+ $this->decorated->putOffExpiration($key, $ttl);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(Key $key)
+ {
+ $this->decorated->delete($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exists(Key $key)
+ {
+ return $this->decorated->exists($key);
+ }
+}
diff --git a/vendor/symfony/lock/Store/SemaphoreStore.php b/vendor/symfony/lock/Store/SemaphoreStore.php
new file mode 100644
index 00000000..a6cc9ea8
--- /dev/null
+++ b/vendor/symfony/lock/Store/SemaphoreStore.php
@@ -0,0 +1,129 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Store;
+
+use Symfony\Component\Lock\Exception\InvalidArgumentException;
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Exception\NotSupportedException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * SemaphoreStore is a StoreInterface implementation using Semaphore as store engine.
+ *
+ * @author Jérémy Derussé
+ */
+class SemaphoreStore implements StoreInterface
+{
+ /**
+ * Returns whether or not the store is supported.
+ *
+ * @param bool|null $blocking when not null, checked again the blocking mode
+ *
+ * @return bool
+ *
+ * @internal
+ */
+ public static function isSupported($blocking = null)
+ {
+ if (!extension_loaded('sysvsem')) {
+ return false;
+ }
+
+ if (false === $blocking && \PHP_VERSION_ID < 50601) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function __construct()
+ {
+ if (!static::isSupported()) {
+ throw new InvalidArgumentException('Semaphore extension (sysvsem) is required');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(Key $key)
+ {
+ $this->lock($key, false);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function waitAndSave(Key $key)
+ {
+ $this->lock($key, true);
+ }
+
+ private function lock(Key $key, $blocking)
+ {
+ if ($key->hasState(__CLASS__)) {
+ return;
+ }
+
+ $resource = sem_get(crc32($key));
+
+ if (\PHP_VERSION_ID < 50601) {
+ if (!$blocking) {
+ throw new NotSupportedException(sprintf('The store "%s" does not supports non blocking locks.', get_class($this)));
+ }
+
+ $acquired = sem_acquire($resource);
+ } else {
+ $acquired = sem_acquire($resource, !$blocking);
+ }
+
+ if (!$acquired) {
+ throw new LockConflictedException();
+ }
+
+ $key->setState(__CLASS__, $resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete(Key $key)
+ {
+ // The lock is maybe not acquired.
+ if (!$key->hasState(__CLASS__)) {
+ return;
+ }
+
+ $resource = $key->getState(__CLASS__);
+
+ sem_release($resource);
+
+ $key->removeState(__CLASS__);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function putOffExpiration(Key $key, $ttl)
+ {
+ // do nothing, the semaphore locks forever.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function exists(Key $key)
+ {
+ return $key->hasState(__CLASS__);
+ }
+}
diff --git a/vendor/symfony/lock/Store/StoreFactory.php b/vendor/symfony/lock/Store/StoreFactory.php
new file mode 100644
index 00000000..a768d197
--- /dev/null
+++ b/vendor/symfony/lock/Store/StoreFactory.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\Lock\Store;
+
+use Symfony\Component\Cache\Traits\RedisProxy;
+use Symfony\Component\Lock\Exception\InvalidArgumentException;
+
+/**
+ * StoreFactory create stores and connections.
+ *
+ * @author Jérémy Derussé
+ */
+class StoreFactory
+{
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client|\Memcached $connection
+ *
+ * @return RedisStore|MemcachedStore
+ */
+ public static function createStore($connection)
+ {
+ if ($connection instanceof \Redis || $connection instanceof \RedisArray || $connection instanceof \RedisCluster || $connection instanceof \Predis\Client || $connection instanceof RedisProxy) {
+ return new RedisStore($connection);
+ }
+ if ($connection instanceof \Memcached) {
+ return new MemcachedStore($connection);
+ }
+
+ throw new InvalidArgumentException(sprintf('Unsupported Connection: %s.', get_class($connection)));
+ }
+}
diff --git a/vendor/symfony/lock/StoreInterface.php b/vendor/symfony/lock/StoreInterface.php
new file mode 100644
index 00000000..985c4476
--- /dev/null
+++ b/vendor/symfony/lock/StoreInterface.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\Lock;
+
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Exception\NotSupportedException;
+
+/**
+ * StoreInterface defines an interface to manipulate a lock store.
+ *
+ * @author Jérémy Derussé
+ */
+interface StoreInterface
+{
+ /**
+ * Stores the resource if it's not locked by someone else.
+ *
+ * @throws LockConflictedException
+ */
+ public function save(Key $key);
+
+ /**
+ * Waits until a key becomes free, then stores the resource.
+ *
+ * If the store does not support this feature it should throw a NotSupportedException.
+ *
+ * @throws LockConflictedException
+ * @throws NotSupportedException
+ */
+ public function waitAndSave(Key $key);
+
+ /**
+ * Extends the ttl of a resource.
+ *
+ * If the store does not support this feature it should throw a NotSupportedException.
+ *
+ * @param float $ttl amount of second to keep the lock in the store
+ *
+ * @throws LockConflictedException
+ * @throws NotSupportedException
+ */
+ public function putOffExpiration(Key $key, $ttl);
+
+ /**
+ * Removes a resource from the storage.
+ */
+ public function delete(Key $key);
+
+ /**
+ * Returns whether or not the resource exists in the storage.
+ *
+ * @return bool
+ */
+ public function exists(Key $key);
+}
diff --git a/vendor/symfony/lock/Strategy/ConsensusStrategy.php b/vendor/symfony/lock/Strategy/ConsensusStrategy.php
new file mode 100644
index 00000000..047820a4
--- /dev/null
+++ b/vendor/symfony/lock/Strategy/ConsensusStrategy.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\Lock\Strategy;
+
+/**
+ * ConsensusStrategy is a StrategyInterface implementation where strictly more than 50% items should be successful.
+ *
+ * @author Jérémy Derussé
+ */
+class ConsensusStrategy implements StrategyInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function isMet($numberOfSuccess, $numberOfItems)
+ {
+ return $numberOfSuccess > ($numberOfItems / 2);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function canBeMet($numberOfFailure, $numberOfItems)
+ {
+ return $numberOfFailure < ($numberOfItems / 2);
+ }
+}
diff --git a/vendor/symfony/lock/Strategy/StrategyInterface.php b/vendor/symfony/lock/Strategy/StrategyInterface.php
new file mode 100644
index 00000000..beaa7280
--- /dev/null
+++ b/vendor/symfony/lock/Strategy/StrategyInterface.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\Lock\Strategy;
+
+/**
+ * StrategyInterface defines an interface to indicate when a quorum is met and can be met.
+ *
+ * @author Jérémy Derussé
+ */
+interface StrategyInterface
+{
+ /**
+ * Returns whether or not the quorum is met.
+ *
+ * @param int $numberOfSuccess
+ * @param int $numberOfItems
+ *
+ * @return bool
+ */
+ public function isMet($numberOfSuccess, $numberOfItems);
+
+ /**
+ * Returns whether or not the quorum *could* be met.
+ *
+ * This method does not mean the quorum *would* be met for sure, but can be useful to stop a process early when you
+ * known there is no chance to meet the quorum.
+ *
+ * @param int $numberOfFailure
+ * @param int $numberOfItems
+ *
+ * @return bool
+ */
+ public function canBeMet($numberOfFailure, $numberOfItems);
+}
diff --git a/vendor/symfony/lock/Strategy/UnanimousStrategy.php b/vendor/symfony/lock/Strategy/UnanimousStrategy.php
new file mode 100644
index 00000000..27404f3e
--- /dev/null
+++ b/vendor/symfony/lock/Strategy/UnanimousStrategy.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\Lock\Strategy;
+
+/**
+ * UnanimousStrategy is a StrategyInterface implementation where 100% of elements should be successful.
+ *
+ * @author Jérémy Derussé
+ */
+class UnanimousStrategy implements StrategyInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function isMet($numberOfSuccess, $numberOfItems)
+ {
+ return $numberOfSuccess === $numberOfItems;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function canBeMet($numberOfFailure, $numberOfItems)
+ {
+ return 0 === $numberOfFailure;
+ }
+}
diff --git a/vendor/symfony/lock/Tests/FactoryTest.php b/vendor/symfony/lock/Tests/FactoryTest.php
new file mode 100644
index 00000000..d6794909
--- /dev/null
+++ b/vendor/symfony/lock/Tests/FactoryTest.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\Lock\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Lock\Factory;
+use Symfony\Component\Lock\LockInterface;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * @author Jérémy Derussé
+ */
+class FactoryTest extends TestCase
+{
+ public function testCreateLock()
+ {
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
+ $factory = new Factory($store);
+ $factory->setLogger($logger);
+
+ $lock = $factory->createLock('foo');
+
+ $this->assertInstanceOf(LockInterface::class, $lock);
+ }
+}
diff --git a/vendor/symfony/lock/Tests/LockTest.php b/vendor/symfony/lock/Tests/LockTest.php
new file mode 100644
index 00000000..7913644b
--- /dev/null
+++ b/vendor/symfony/lock/Tests/LockTest.php
@@ -0,0 +1,254 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\Lock;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * @author Jérémy Derussé
+ */
+class LockTest extends TestCase
+{
+ public function testAcquireNoBlocking()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store);
+
+ $store
+ ->expects($this->once())
+ ->method('save');
+
+ $this->assertTrue($lock->acquire(false));
+ }
+
+ public function testAcquireReturnsFalse()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store);
+
+ $store
+ ->expects($this->once())
+ ->method('save')
+ ->willThrowException(new LockConflictedException());
+
+ $this->assertFalse($lock->acquire(false));
+ }
+
+ public function testAcquireBlocking()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store);
+
+ $store
+ ->expects($this->never())
+ ->method('save');
+ $store
+ ->expects($this->once())
+ ->method('waitAndSave');
+
+ $this->assertTrue($lock->acquire(true));
+ }
+
+ public function testAcquireSetsTtl()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ $store
+ ->expects($this->once())
+ ->method('save');
+ $store
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, 10);
+
+ $lock->acquire();
+ }
+
+ public function testRefresh()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ $store
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, 10);
+
+ $lock->refresh();
+ }
+
+ public function testIsAquired()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ $store
+ ->expects($this->any())
+ ->method('exists')
+ ->with($key)
+ ->will($this->onConsecutiveCalls(true, false));
+
+ $this->assertTrue($lock->isAcquired());
+ }
+
+ public function testRelease()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ $store
+ ->expects($this->once())
+ ->method('delete')
+ ->with($key);
+
+ $store
+ ->expects($this->once())
+ ->method('exists')
+ ->with($key)
+ ->willReturn(false);
+
+ $lock->release();
+ }
+
+ public function testReleaseOnDestruction()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ $store
+ ->method('exists')
+ ->willReturnOnConsecutiveCalls(array(true, false))
+ ;
+ $store
+ ->expects($this->once())
+ ->method('delete')
+ ;
+
+ $lock->acquire(false);
+ unset($lock);
+ }
+
+ public function testNoAutoReleaseWhenNotConfigured()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10, false);
+
+ $store
+ ->method('exists')
+ ->willReturnOnConsecutiveCalls(array(true, false))
+ ;
+ $store
+ ->expects($this->never())
+ ->method('delete')
+ ;
+
+ $lock->acquire(false);
+ unset($lock);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\LockReleasingException
+ */
+ public function testReleaseThrowsExceptionIfNotWellDeleted()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ $store
+ ->expects($this->once())
+ ->method('delete')
+ ->with($key);
+
+ $store
+ ->expects($this->once())
+ ->method('exists')
+ ->with($key)
+ ->willReturn(true);
+
+ $lock->release();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\LockReleasingException
+ */
+ public function testReleaseThrowsAndLog()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10, true);
+ $lock->setLogger($logger);
+
+ $logger->expects($this->atLeastOnce())
+ ->method('notice')
+ ->with('Failed to release the "{resource}" lock.', array('resource' => $key));
+
+ $store
+ ->expects($this->once())
+ ->method('delete')
+ ->with($key);
+
+ $store
+ ->expects($this->once())
+ ->method('exists')
+ ->with($key)
+ ->willReturn(true);
+
+ $lock->release();
+ }
+
+ /**
+ * @dataProvider provideExpiredDates
+ */
+ public function testExpiration($ttls, $expected)
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $store = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $lock = new Lock($key, $store, 10);
+
+ foreach ($ttls as $ttl) {
+ if (null === $ttl) {
+ $key->resetLifetime();
+ } else {
+ $key->reduceLifetime($ttl);
+ }
+ }
+ $this->assertSame($expected, $lock->isExpired());
+ }
+
+ public function provideExpiredDates()
+ {
+ yield array(array(-0.1), true);
+ yield array(array(0.1, -0.1), true);
+ yield array(array(-0.1, 0.1), true);
+
+ yield array(array(), false);
+ yield array(array(0.1), false);
+ yield array(array(-0.1, null), false);
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/AbstractRedisStoreTest.php b/vendor/symfony/lock/Tests/Store/AbstractRedisStoreTest.php
new file mode 100644
index 00000000..4b9c81bd
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/AbstractRedisStoreTest.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\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Store\RedisStore;
+
+/**
+ * @author Jérémy Derussé
+ */
+abstract class AbstractRedisStoreTest extends AbstractStoreTest
+{
+ use ExpiringStoreTestTrait;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getClockDelay()
+ {
+ return 250000;
+ }
+
+ /**
+ * Return a RedisConnection.
+ *
+ * @return \Redis|\RedisArray|\RedisCluster|\Predis\Client
+ */
+ abstract protected function getRedisConnection();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStore()
+ {
+ return new RedisStore($this->getRedisConnection());
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/AbstractStoreTest.php b/vendor/symfony/lock/Tests/Store/AbstractStoreTest.php
new file mode 100644
index 00000000..2ab030b2
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/AbstractStoreTest.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\Lock\Tests\Store;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * @author Jérémy Derussé
+ */
+abstract class AbstractStoreTest extends TestCase
+{
+ /**
+ * @return StoreInterface
+ */
+ abstract protected function getStore();
+
+ public function testSave()
+ {
+ $store = $this->getStore();
+
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->assertFalse($store->exists($key));
+ $store->save($key);
+ $this->assertTrue($store->exists($key));
+ $store->delete($key);
+ $this->assertFalse($store->exists($key));
+ }
+
+ public function testSaveWithDifferentResources()
+ {
+ $store = $this->getStore();
+
+ $key1 = new Key(uniqid(__METHOD__, true));
+ $key2 = new Key(uniqid(__METHOD__, true));
+
+ $store->save($key1);
+ $this->assertTrue($store->exists($key1));
+ $this->assertFalse($store->exists($key2));
+
+ $store->save($key2);
+ $this->assertTrue($store->exists($key1));
+ $this->assertTrue($store->exists($key2));
+
+ $store->delete($key1);
+ $this->assertFalse($store->exists($key1));
+ $this->assertTrue($store->exists($key2));
+
+ $store->delete($key2);
+ $this->assertFalse($store->exists($key1));
+ $this->assertFalse($store->exists($key2));
+ }
+
+ public function testSaveWithDifferentKeysOnSameResources()
+ {
+ $store = $this->getStore();
+
+ $resource = uniqid(__METHOD__, true);
+ $key1 = new Key($resource);
+ $key2 = new Key($resource);
+
+ $store->save($key1);
+ $this->assertTrue($store->exists($key1));
+ $this->assertFalse($store->exists($key2));
+
+ try {
+ $store->save($key2);
+ $this->fail('The store shouldn\'t save the second key');
+ } catch (LockConflictedException $e) {
+ }
+
+ // The failure of previous attempt should not impact the state of current locks
+ $this->assertTrue($store->exists($key1));
+ $this->assertFalse($store->exists($key2));
+
+ $store->delete($key1);
+ $this->assertFalse($store->exists($key1));
+ $this->assertFalse($store->exists($key2));
+
+ $store->save($key2);
+ $this->assertFalse($store->exists($key1));
+ $this->assertTrue($store->exists($key2));
+
+ $store->delete($key2);
+ $this->assertFalse($store->exists($key1));
+ $this->assertFalse($store->exists($key2));
+ }
+
+ public function testSaveTwice()
+ {
+ $store = $this->getStore();
+
+ $resource = uniqid(__METHOD__, true);
+ $key = new Key($resource);
+
+ $store->save($key);
+ $store->save($key);
+ // just asserts it don't throw an exception
+ $this->addToAssertionCount(1);
+
+ $store->delete($key);
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/BlockingStoreTestTrait.php b/vendor/symfony/lock/Tests/Store/BlockingStoreTestTrait.php
new file mode 100644
index 00000000..93e5a547
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/BlockingStoreTestTrait.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\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * @author Jérémy Derussé
+ */
+trait BlockingStoreTestTrait
+{
+ /**
+ * @see AbstractStoreTest::getStore()
+ */
+ abstract protected function getStore();
+
+ /**
+ * Tests blocking locks thanks to pcntl.
+ *
+ * This test is time sensible: the $clockDelay could be adjust.
+ *
+ * @requires extension pcntl
+ * @requires function pcntl_sigwaitinfo
+ */
+ public function testBlockingLocks()
+ {
+ // Amount a microsecond used to order async actions
+ $clockDelay = 50000;
+
+ if (\PHP_VERSION_ID < 50600 || defined('HHVM_VERSION_ID')) {
+ $this->markTestSkipped('The PHP engine does not keep resource in child forks');
+
+ return;
+ }
+
+ /** @var StoreInterface $store */
+ $store = $this->getStore();
+ $key = new Key(uniqid(__METHOD__, true));
+ $parentPID = posix_getpid();
+
+ // Block SIGHUP signal
+ pcntl_sigprocmask(SIG_BLOCK, array(SIGHUP));
+
+ if ($childPID = pcntl_fork()) {
+ // Wait the start of the child
+ pcntl_sigwaitinfo(array(SIGHUP), $info);
+
+ try {
+ // This call should failed given the lock should already by acquired by the child
+ $store->save($key);
+ $this->fail('The store saves a locked key.');
+ } catch (LockConflictedException $e) {
+ }
+
+ // send the ready signal to the child
+ posix_kill($childPID, SIGHUP);
+
+ // This call should be blocked by the child #1
+ $store->waitAndSave($key);
+ $this->assertTrue($store->exists($key));
+ $store->delete($key);
+
+ // Now, assert the child process worked well
+ pcntl_waitpid($childPID, $status1);
+ $this->assertSame(0, pcntl_wexitstatus($status1), 'The child process couldn\'t lock the resource');
+ } else {
+ // Block SIGHUP signal
+ pcntl_sigprocmask(SIG_BLOCK, array(SIGHUP));
+ try {
+ $store->save($key);
+ // send the ready signal to the parent
+ posix_kill($parentPID, SIGHUP);
+
+ // Wait for the parent to be ready
+ pcntl_sigwaitinfo(array(SIGHUP), $info);
+
+ // Wait ClockDelay to let parent assert to finish
+ usleep($clockDelay);
+ $store->delete($key);
+ exit(0);
+ } catch (\Exception $e) {
+ exit(1);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/CombinedStoreTest.php b/vendor/symfony/lock/Tests/Store/CombinedStoreTest.php
new file mode 100644
index 00000000..c7232d40
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/CombinedStoreTest.php
@@ -0,0 +1,356 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Exception\LockConflictedException;
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\Strategy\UnanimousStrategy;
+use Symfony\Component\Lock\Strategy\StrategyInterface;
+use Symfony\Component\Lock\Store\CombinedStore;
+use Symfony\Component\Lock\Store\RedisStore;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * @author Jérémy Derussé
+ */
+class CombinedStoreTest extends AbstractStoreTest
+{
+ use ExpiringStoreTestTrait;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getClockDelay()
+ {
+ return 250000;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStore()
+ {
+ $redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
+ try {
+ $redis->connect();
+ } catch (\Exception $e) {
+ self::markTestSkipped($e->getMessage());
+ }
+
+ return new CombinedStore(array(new RedisStore($redis)), new UnanimousStrategy());
+ }
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ private $strategy;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ private $store1;
+ /** @var \PHPUnit_Framework_MockObject_MockObject */
+ private $store2;
+ /** @var CombinedStore */
+ private $store;
+
+ public function setup()
+ {
+ $this->strategy = $this->getMockBuilder(StrategyInterface::class)->getMock();
+ $this->store1 = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $this->store2 = $this->getMockBuilder(StoreInterface::class)->getMock();
+
+ $this->store = new CombinedStore(array($this->store1, $this->store2), $this->strategy);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\LockConflictedException
+ */
+ public function testSaveThrowsExceptionOnFailure()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->store1
+ ->expects($this->once())
+ ->method('save')
+ ->with($key)
+ ->willThrowException(new LockConflictedException());
+ $this->store2
+ ->expects($this->once())
+ ->method('save')
+ ->with($key)
+ ->willThrowException(new LockConflictedException());
+
+ $this->strategy
+ ->expects($this->any())
+ ->method('canBeMet')
+ ->willReturn(true);
+ $this->strategy
+ ->expects($this->any())
+ ->method('isMet')
+ ->willReturn(false);
+
+ $this->store->save($key);
+ }
+
+ public function testSaveCleanupOnFailure()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->store1
+ ->expects($this->once())
+ ->method('save')
+ ->with($key)
+ ->willThrowException(new LockConflictedException());
+ $this->store2
+ ->expects($this->once())
+ ->method('save')
+ ->with($key)
+ ->willThrowException(new LockConflictedException());
+
+ $this->store1
+ ->expects($this->once())
+ ->method('delete');
+ $this->store2
+ ->expects($this->once())
+ ->method('delete');
+
+ $this->strategy
+ ->expects($this->any())
+ ->method('canBeMet')
+ ->willReturn(true);
+ $this->strategy
+ ->expects($this->any())
+ ->method('isMet')
+ ->willReturn(false);
+
+ try {
+ $this->store->save($key);
+ } catch (LockConflictedException $e) {
+ // Catch the exception given this is not what we want to assert in this tests
+ }
+ }
+
+ public function testSaveAbortWhenStrategyCantBeMet()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->store1
+ ->expects($this->once())
+ ->method('save')
+ ->with($key)
+ ->willThrowException(new LockConflictedException());
+ $this->store2
+ ->expects($this->never())
+ ->method('save');
+
+ $this->strategy
+ ->expects($this->once())
+ ->method('canBeMet')
+ ->willReturn(false);
+ $this->strategy
+ ->expects($this->any())
+ ->method('isMet')
+ ->willReturn(false);
+
+ try {
+ $this->store->save($key);
+ } catch (LockConflictedException $e) {
+ // Catch the exception given this is not what we want to assert in this tests
+ }
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\LockConflictedException
+ */
+ public function testputOffExpirationThrowsExceptionOnFailure()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $ttl = random_int(1, 10);
+
+ $this->store1
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, $this->lessThanOrEqual($ttl))
+ ->willThrowException(new LockConflictedException());
+ $this->store2
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, $this->lessThanOrEqual($ttl))
+ ->willThrowException(new LockConflictedException());
+
+ $this->strategy
+ ->expects($this->any())
+ ->method('canBeMet')
+ ->willReturn(true);
+ $this->strategy
+ ->expects($this->any())
+ ->method('isMet')
+ ->willReturn(false);
+
+ $this->store->putOffExpiration($key, $ttl);
+ }
+
+ public function testputOffExpirationCleanupOnFailure()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $ttl = random_int(1, 10);
+
+ $this->store1
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, $this->lessThanOrEqual($ttl))
+ ->willThrowException(new LockConflictedException());
+ $this->store2
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, $this->lessThanOrEqual($ttl))
+ ->willThrowException(new LockConflictedException());
+
+ $this->store1
+ ->expects($this->once())
+ ->method('delete');
+ $this->store2
+ ->expects($this->once())
+ ->method('delete');
+
+ $this->strategy
+ ->expects($this->any())
+ ->method('canBeMet')
+ ->willReturn(true);
+ $this->strategy
+ ->expects($this->any())
+ ->method('isMet')
+ ->willReturn(false);
+
+ try {
+ $this->store->putOffExpiration($key, $ttl);
+ } catch (LockConflictedException $e) {
+ // Catch the exception given this is not what we want to assert in this tests
+ }
+ }
+
+ public function testputOffExpirationAbortWhenStrategyCantBeMet()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $ttl = random_int(1, 10);
+
+ $this->store1
+ ->expects($this->once())
+ ->method('putOffExpiration')
+ ->with($key, $this->lessThanOrEqual($ttl))
+ ->willThrowException(new LockConflictedException());
+ $this->store2
+ ->expects($this->never())
+ ->method('putOffExpiration');
+
+ $this->strategy
+ ->expects($this->once())
+ ->method('canBeMet')
+ ->willReturn(false);
+ $this->strategy
+ ->expects($this->any())
+ ->method('isMet')
+ ->willReturn(false);
+
+ try {
+ $this->store->putOffExpiration($key, $ttl);
+ } catch (LockConflictedException $e) {
+ // Catch the exception given this is not what we want to assert in this tests
+ }
+ }
+
+ public function testPutOffExpirationIgnoreNonExpiringStorage()
+ {
+ $store1 = $this->getMockBuilder(StoreInterface::class)->getMock();
+ $store2 = $this->getMockBuilder(StoreInterface::class)->getMock();
+
+ $store = new CombinedStore(array($store1, $store2), $this->strategy);
+
+ $key = new Key(uniqid(__METHOD__, true));
+ $ttl = random_int(1, 10);
+
+ $this->strategy
+ ->expects($this->any())
+ ->method('canBeMet')
+ ->willReturn(true);
+ $this->strategy
+ ->expects($this->once())
+ ->method('isMet')
+ ->with(2, 2)
+ ->willReturn(true);
+
+ $store->putOffExpiration($key, $ttl);
+ }
+
+ public function testExistsDontAskToEveryBody()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->store1
+ ->expects($this->any())
+ ->method('exists')
+ ->with($key)
+ ->willReturn(false);
+ $this->store2
+ ->expects($this->never())
+ ->method('exists');
+
+ $this->strategy
+ ->expects($this->any())
+ ->method('canBeMet')
+ ->willReturn(true);
+ $this->strategy
+ ->expects($this->once())
+ ->method('isMet')
+ ->willReturn(true);
+
+ $this->assertTrue($this->store->exists($key));
+ }
+
+ public function testExistsAbortWhenStrategyCantBeMet()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->store1
+ ->expects($this->any())
+ ->method('exists')
+ ->with($key)
+ ->willReturn(false);
+ $this->store2
+ ->expects($this->never())
+ ->method('exists');
+
+ $this->strategy
+ ->expects($this->once())
+ ->method('canBeMet')
+ ->willReturn(false);
+ $this->strategy
+ ->expects($this->once())
+ ->method('isMet')
+ ->willReturn(false);
+
+ $this->assertFalse($this->store->exists($key));
+ }
+
+ public function testDeleteDontStopOnFailure()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ $this->store1
+ ->expects($this->once())
+ ->method('delete')
+ ->with($key)
+ ->willThrowException(new \Exception());
+ $this->store2
+ ->expects($this->once())
+ ->method('delete')
+ ->with($key);
+
+ $this->store->delete($key);
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/ExpiringStoreTestTrait.php b/vendor/symfony/lock/Tests/Store/ExpiringStoreTestTrait.php
new file mode 100644
index 00000000..10b13273
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/ExpiringStoreTestTrait.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\StoreInterface;
+
+/**
+ * @author Jérémy Derussé
+ */
+trait ExpiringStoreTestTrait
+{
+ /**
+ * Amount a microsecond used to order async actions.
+ *
+ * @return int
+ */
+ abstract protected function getClockDelay();
+
+ /**
+ * @see AbstractStoreTest::getStore()
+ */
+ abstract protected function getStore();
+
+ /**
+ * Tests the store automatically delete the key when it expire.
+ *
+ * This test is time sensible: the $clockDelay could be adjust.
+ */
+ public function testExpiration()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+ $clockDelay = $this->getClockDelay();
+
+ /** @var StoreInterface $store */
+ $store = $this->getStore();
+
+ $store->save($key);
+ $store->putOffExpiration($key, $clockDelay / 1000000);
+ $this->assertTrue($store->exists($key));
+
+ usleep(2 * $clockDelay);
+ $this->assertFalse($store->exists($key));
+ }
+
+ /**
+ * Tests the store thrown exception when TTL expires.
+ *
+ * @expectedException \Symfony\Component\Lock\Exception\LockExpiredException
+ */
+ public function testAbortAfterExpiration()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ /** @var StoreInterface $store */
+ $store = $this->getStore();
+
+ $store->save($key);
+ $store->putOffExpiration($key, 1 / 1000000);
+ }
+
+ /**
+ * Tests the refresh can push the limits to the expiration.
+ *
+ * This test is time sensible: the $clockDelay could be adjust.
+ */
+ public function testRefreshLock()
+ {
+ // Amount a microsecond used to order async actions
+ $clockDelay = $this->getClockDelay();
+
+ // Amount a microsecond used to order async actions
+ $key = new Key(uniqid(__METHOD__, true));
+
+ /** @var StoreInterface $store */
+ $store = $this->getStore();
+
+ $store->save($key);
+ $store->putOffExpiration($key, $clockDelay / 1000000);
+ $this->assertTrue($store->exists($key));
+
+ usleep(2 * $clockDelay);
+ $this->assertFalse($store->exists($key));
+ }
+
+ public function testSetExpiration()
+ {
+ $key = new Key(uniqid(__METHOD__, true));
+
+ /** @var StoreInterface $store */
+ $store = $this->getStore();
+
+ $store->save($key);
+ $store->putOffExpiration($key, 1);
+ $this->assertGreaterThanOrEqual(0, $key->getRemainingLifetime());
+ $this->assertLessThanOrEqual(1, $key->getRemainingLifetime());
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/FlockStoreTest.php b/vendor/symfony/lock/Tests/Store/FlockStoreTest.php
new file mode 100644
index 00000000..ef3650c3
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/FlockStoreTest.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Key;
+use Symfony\Component\Lock\Store\FlockStore;
+
+/**
+ * @author Jérémy Derussé
+ */
+class FlockStoreTest extends AbstractStoreTest
+{
+ use BlockingStoreTestTrait;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getStore()
+ {
+ return new FlockStore();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\InvalidArgumentException
+ * @expectedExceptionMessage The directory "/a/b/c/d/e" is not writable.
+ */
+ public function testConstructWhenRepositoryDoesNotExist()
+ {
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ new FlockStore('/a/b/c/d/e');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Lock\Exception\InvalidArgumentException
+ * @expectedExceptionMessage The directory "/" is not writable.
+ */
+ public function testConstructWhenRepositoryIsNotWriteable()
+ {
+ if (!getenv('USER') || 'root' === getenv('USER')) {
+ $this->markTestSkipped('This test will fail if run under superuser');
+ }
+
+ new FlockStore('/');
+ }
+
+ public function testSaveSanitizeName()
+ {
+ $store = $this->getStore();
+
+ $key = new Key('');
+
+ $file = sprintf(
+ '%s/sf.-php-echo-hello-word-.%s.lock',
+ sys_get_temp_dir(),
+ strtr(substr(base64_encode(hash('sha256', $key, true)), 0, 7), '/', '_')
+ );
+ // ensure the file does not exist before the store
+ @unlink($file);
+
+ $store->save($key);
+
+ $this->assertFileExists($file);
+
+ $store->delete($key);
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/MemcachedStoreTest.php b/vendor/symfony/lock/Tests/Store/MemcachedStoreTest.php
new file mode 100644
index 00000000..f4ab571f
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/MemcachedStoreTest.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\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Store\MemcachedStore;
+
+/**
+ * @author Jérémy Derussé
+ *
+ * @requires extension memcached
+ */
+class MemcachedStoreTest extends AbstractStoreTest
+{
+ use ExpiringStoreTestTrait;
+
+ public static function setupBeforeClass()
+ {
+ $memcached = new \Memcached();
+ $memcached->addServer(getenv('MEMCACHED_HOST'), 11211);
+ $memcached->get('foo');
+ $code = $memcached->getResultCode();
+
+ if (\Memcached::RES_SUCCESS !== $code && \Memcached::RES_NOTFOUND !== $code) {
+ self::markTestSkipped('Unable to connect to the memcache host');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getClockDelay()
+ {
+ return 1000000;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStore()
+ {
+ $memcached = new \Memcached();
+ $memcached->addServer(getenv('MEMCACHED_HOST'), 11211);
+
+ return new MemcachedStore($memcached);
+ }
+
+ public function testAbortAfterExpiration()
+ {
+ $this->markTestSkipped('Memcached expects a TTL greater than 1 sec. Simulating a slow network is too hard');
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/PredisStoreTest.php b/vendor/symfony/lock/Tests/Store/PredisStoreTest.php
new file mode 100644
index 00000000..621affec
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/PredisStoreTest.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\Lock\Tests\Store;
+
+/**
+ * @author Jérémy Derussé
+ */
+class PredisStoreTest extends AbstractRedisStoreTest
+{
+ public static function setupBeforeClass()
+ {
+ $redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
+ try {
+ $redis->connect();
+ } catch (\Exception $e) {
+ self::markTestSkipped($e->getMessage());
+ }
+ }
+
+ protected function getRedisConnection()
+ {
+ $redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
+ $redis->connect();
+
+ return $redis;
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/RedisArrayStoreTest.php b/vendor/symfony/lock/Tests/Store/RedisArrayStoreTest.php
new file mode 100644
index 00000000..180da461
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/RedisArrayStoreTest.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\Lock\Tests\Store;
+
+/**
+ * @author Jérémy Derussé
+ *
+ * @requires extension redis
+ */
+class RedisArrayStoreTest extends AbstractRedisStoreTest
+{
+ public static function setupBeforeClass()
+ {
+ if (!class_exists('RedisArray')) {
+ self::markTestSkipped('The RedisArray class is required.');
+ }
+ if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
+ $e = error_get_last();
+ self::markTestSkipped($e['message']);
+ }
+ }
+
+ protected function getRedisConnection()
+ {
+ $redis = new \RedisArray(array(getenv('REDIS_HOST')));
+
+ return $redis;
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/RedisStoreTest.php b/vendor/symfony/lock/Tests/Store/RedisStoreTest.php
new file mode 100644
index 00000000..6c7d2441
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/RedisStoreTest.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\Lock\Tests\Store;
+
+/**
+ * @author Jérémy Derussé
+ *
+ * @requires extension redis
+ */
+class RedisStoreTest extends AbstractRedisStoreTest
+{
+ public static function setupBeforeClass()
+ {
+ if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
+ $e = error_get_last();
+ self::markTestSkipped($e['message']);
+ }
+ }
+
+ protected function getRedisConnection()
+ {
+ $redis = new \Redis();
+ $redis->connect(getenv('REDIS_HOST'));
+
+ return $redis;
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/RetryTillSaveStoreTest.php b/vendor/symfony/lock/Tests/Store/RetryTillSaveStoreTest.php
new file mode 100644
index 00000000..febd48f2
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/RetryTillSaveStoreTest.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\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Store\RedisStore;
+use Symfony\Component\Lock\Store\RetryTillSaveStore;
+
+/**
+ * @author Jérémy Derussé
+ */
+class RetryTillSaveStoreTest extends AbstractStoreTest
+{
+ use BlockingStoreTestTrait;
+
+ public function getStore()
+ {
+ $redis = new \Predis\Client('tcp://'.getenv('REDIS_HOST').':6379');
+ try {
+ $redis->connect();
+ } catch (\Exception $e) {
+ self::markTestSkipped($e->getMessage());
+ }
+
+ return new RetryTillSaveStore(new RedisStore($redis), 100, 100);
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Store/SemaphoreStoreTest.php b/vendor/symfony/lock/Tests/Store/SemaphoreStoreTest.php
new file mode 100644
index 00000000..bb37ec1f
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Store/SemaphoreStoreTest.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\Lock\Tests\Store;
+
+use Symfony\Component\Lock\Store\SemaphoreStore;
+
+/**
+ * @author Jérémy Derussé
+ *
+ * @requires extension sysvsem
+ */
+class SemaphoreStoreTest extends AbstractStoreTest
+{
+ use BlockingStoreTestTrait;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getStore()
+ {
+ if (\PHP_VERSION_ID < 50601) {
+ $this->markTestSkipped('Non blocking semaphore are supported by PHP version greater or equals than 5.6.1');
+ }
+
+ return new SemaphoreStore();
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Strategy/ConsensusStrategyTest.php b/vendor/symfony/lock/Tests/Strategy/ConsensusStrategyTest.php
new file mode 100644
index 00000000..87e83a99
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Strategy/ConsensusStrategyTest.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\Lock\Tests\Strategy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Lock\Strategy\ConsensusStrategy;
+
+/**
+ * @author Jérémy Derussé
+ */
+class ConsensusStrategyTest extends TestCase
+{
+ /** @var ConsensusStrategy */
+ private $strategy;
+
+ public function setup()
+ {
+ $this->strategy = new ConsensusStrategy();
+ }
+
+ public function provideMetResults()
+ {
+ // success, failure, total, isMet
+ yield array(3, 0, 3, true);
+ yield array(2, 1, 3, true);
+ yield array(2, 0, 3, true);
+ yield array(1, 2, 3, false);
+ yield array(1, 1, 3, false);
+ yield array(1, 0, 3, false);
+ yield array(0, 3, 3, false);
+ yield array(0, 2, 3, false);
+ yield array(0, 1, 3, false);
+ yield array(0, 0, 3, false);
+
+ yield array(2, 0, 2, true);
+ yield array(1, 1, 2, false);
+ yield array(1, 0, 2, false);
+ yield array(0, 2, 2, false);
+ yield array(0, 1, 2, false);
+ yield array(0, 0, 2, false);
+ }
+
+ public function provideIndeterminate()
+ {
+ // success, failure, total, canBeMet
+ yield array(3, 0, 3, true);
+ yield array(2, 1, 3, true);
+ yield array(2, 0, 3, true);
+ yield array(1, 2, 3, false);
+ yield array(1, 1, 3, true);
+ yield array(1, 0, 3, true);
+ yield array(0, 3, 3, false);
+ yield array(0, 2, 3, false);
+ yield array(0, 1, 3, true);
+ yield array(0, 0, 3, true);
+
+ yield array(2, 0, 2, true);
+ yield array(1, 1, 2, false);
+ yield array(1, 0, 2, true);
+ yield array(0, 2, 2, false);
+ yield array(0, 1, 2, false);
+ yield array(0, 0, 2, true);
+ }
+
+ /**
+ * @dataProvider provideMetResults
+ */
+ public function testMet($success, $failure, $total, $isMet)
+ {
+ $this->assertSame($isMet, $this->strategy->isMet($success, $total));
+ }
+
+ /**
+ * @dataProvider provideIndeterminate
+ */
+ public function testCanBeMet($success, $failure, $total, $isMet)
+ {
+ $this->assertSame($isMet, $this->strategy->canBeMet($failure, $total));
+ }
+}
diff --git a/vendor/symfony/lock/Tests/Strategy/UnanimousStrategyTest.php b/vendor/symfony/lock/Tests/Strategy/UnanimousStrategyTest.php
new file mode 100644
index 00000000..beff54a6
--- /dev/null
+++ b/vendor/symfony/lock/Tests/Strategy/UnanimousStrategyTest.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\Lock\Tests\Strategy;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Lock\Strategy\UnanimousStrategy;
+
+/**
+ * @author Jérémy Derussé
+ */
+class UnanimousStrategyTest extends TestCase
+{
+ /** @var UnanimousStrategy */
+ private $strategy;
+
+ public function setup()
+ {
+ $this->strategy = new UnanimousStrategy();
+ }
+
+ public function provideMetResults()
+ {
+ // success, failure, total, isMet
+ yield array(3, 0, 3, true);
+ yield array(2, 1, 3, false);
+ yield array(2, 0, 3, false);
+ yield array(1, 2, 3, false);
+ yield array(1, 1, 3, false);
+ yield array(1, 0, 3, false);
+ yield array(0, 3, 3, false);
+ yield array(0, 2, 3, false);
+ yield array(0, 1, 3, false);
+ yield array(0, 0, 3, false);
+
+ yield array(2, 0, 2, true);
+ yield array(1, 1, 2, false);
+ yield array(1, 0, 2, false);
+ yield array(0, 2, 2, false);
+ yield array(0, 1, 2, false);
+ yield array(0, 0, 2, false);
+ }
+
+ public function provideIndeterminate()
+ {
+ // success, failure, total, canBeMet
+ yield array(3, 0, 3, true);
+ yield array(2, 1, 3, false);
+ yield array(2, 0, 3, true);
+ yield array(1, 2, 3, false);
+ yield array(1, 1, 3, false);
+ yield array(1, 0, 3, true);
+ yield array(0, 3, 3, false);
+ yield array(0, 2, 3, false);
+ yield array(0, 1, 3, false);
+ yield array(0, 0, 3, true);
+
+ yield array(2, 0, 2, true);
+ yield array(1, 1, 2, false);
+ yield array(1, 0, 2, true);
+ yield array(0, 2, 2, false);
+ yield array(0, 1, 2, false);
+ yield array(0, 0, 2, true);
+ }
+
+ /**
+ * @dataProvider provideMetResults
+ */
+ public function testMet($success, $failure, $total, $isMet)
+ {
+ $this->assertSame($isMet, $this->strategy->isMet($success, $total));
+ }
+
+ /**
+ * @dataProvider provideIndeterminate
+ */
+ public function testCanBeMet($success, $failure, $total, $isMet)
+ {
+ $this->assertSame($isMet, $this->strategy->canBeMet($failure, $total));
+ }
+}
diff --git a/vendor/symfony/lock/composer.json b/vendor/symfony/lock/composer.json
new file mode 100644
index 00000000..9a6e7687
--- /dev/null
+++ b/vendor/symfony/lock/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/lock",
+ "type": "library",
+ "description": "Symfony Lock Component",
+ "keywords": ["locking", "redlock", "mutex", "semaphore", "flock", "cas"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jérémy Derussé",
+ "email": "jeremy@derusse.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": "^5.5.9|>=7.0.8",
+ "symfony/polyfill-php70": "~1.0",
+ "psr/log": "~1.0"
+ },
+ "require-dev": {
+ "predis/predis": "~1.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Lock\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/lock/phpunit.xml.dist b/vendor/symfony/lock/phpunit.xml.dist
new file mode 100644
index 00000000..be3ca215
--- /dev/null
+++ b/vendor/symfony/lock/phpunit.xml.dist
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+ ./Tests
+ ./vendor
+
+
+
+
diff --git a/vendor/symfony/polyfill-php70/LICENSE b/vendor/symfony/polyfill-php70/LICENSE
new file mode 100644
index 00000000..24fa32c2
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2018 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/polyfill-php70/Php70.php b/vendor/symfony/polyfill-php70/Php70.php
new file mode 100644
index 00000000..39e66450
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Php70.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Php70;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class Php70
+{
+ public static function intdiv($dividend, $divisor)
+ {
+ $dividend = self::intArg($dividend, __FUNCTION__, 1);
+ $divisor = self::intArg($divisor, __FUNCTION__, 2);
+
+ if (0 === $divisor) {
+ throw new \DivisionByZeroError('Division by zero');
+ }
+ if (-1 === $divisor && ~PHP_INT_MAX === $dividend) {
+ throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer');
+ }
+
+ return ($dividend - ($dividend % $divisor)) / $divisor;
+ }
+
+ public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0)
+ {
+ $count = 0;
+ $result = (string) $subject;
+ if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) {
+ return $result;
+ }
+
+ foreach ($patterns as $pattern => $callback) {
+ $result = preg_replace_callback($pattern, $callback, $result, $limit, $c);
+ $count += $c;
+ }
+
+ return $result;
+ }
+
+ public static function error_clear_last()
+ {
+ static $handler;
+ if (!$handler) {
+ $handler = function() { return false; };
+ }
+ set_error_handler($handler);
+ @trigger_error('');
+ restore_error_handler();
+ }
+
+ private static function intArg($value, $caller, $pos)
+ {
+ if (\is_int($value)) {
+ return $value;
+ }
+ if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) {
+ throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, gettype($value)));
+ }
+
+ return (int) $value;
+ }
+}
diff --git a/vendor/symfony/polyfill-php70/README.md b/vendor/symfony/polyfill-php70/README.md
new file mode 100644
index 00000000..04988c6f
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/README.md
@@ -0,0 +1,28 @@
+Symfony Polyfill / Php70
+========================
+
+This component provides features unavailable in releases prior to PHP 7.0:
+
+- [`intdiv`](http://php.net/intdiv)
+- [`preg_replace_callback_array`](http://php.net/preg_replace_callback_array)
+- [`error_clear_last`](http://php.net/error_clear_last)
+- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat))
+- [`*Error` throwable classes](http://php.net/Error)
+- [`PHP_INT_MIN`](http://php.net/manual/en/reserved.constants.php#constant.php-int-min)
+- `SessionUpdateTimestampHandlerInterface`
+
+More information can be found in the
+[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
+
+Compatibility notes
+===================
+
+To write portable code between PHP5 and PHP7, some care must be taken:
+- `\*Error` exceptions must be caught before `\Exception`;
+- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be
+ verified using `isset($e['message'][0])` instead of `null !== $e`.
+
+License
+=======
+
+This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
new file mode 100644
index 00000000..68191244
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php
@@ -0,0 +1,5 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php70 as p;
+
+if (PHP_VERSION_ID < 70000) {
+ if (!defined('PHP_INT_MIN')) {
+ define('PHP_INT_MIN', ~PHP_INT_MAX);
+ }
+ if (!function_exists('intdiv')) {
+ function intdiv($dividend, $divisor) { return p\Php70::intdiv($dividend, $divisor); }
+ }
+ if (!function_exists('preg_replace_callback_array')) {
+ function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) { return p\Php70::preg_replace_callback_array($patterns, $subject, $limit, $count); }
+ }
+ if (!function_exists('error_clear_last')) {
+ function error_clear_last() { return p\Php70::error_clear_last(); }
+ }
+}
diff --git a/vendor/symfony/polyfill-php70/composer.json b/vendor/symfony/polyfill-php70/composer.json
new file mode 100644
index 00000000..871045d2
--- /dev/null
+++ b/vendor/symfony/polyfill-php70/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/polyfill-php70",
+ "type": "library",
+ "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+ "keywords": ["polyfill", "shim", "compatibility", "portable"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3",
+ "paragonie/random_compat": "~1.0|~2.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Polyfill\\Php70\\": "" },
+ "files": [ "bootstrap.php" ],
+ "classmap": [ "Resources/stubs" ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.8-dev"
+ }
+ }
+}