<?php

namespace Rede;

class Cart implements RedeSerializable
{
    use SerializeTrait;

    /**
     * @var Address
     */
    private $billing;

    /**
     * @var Consumer
     */
    private $consumer;

    /**
     * @var Environment
     */
    private $environment;

    /**
     * @var Iata
     */
    private $iata;

    /**
     * @var array[Item]
     */
    private $items;

    /**
     *
     * @var Address
     */
    private $shipping;

    /**
     * @param int $type
     *
     * @return Address
     */
    public function address($type = Address::BOTH)
    {
        $address = new Address();

        if ($type & Address::BILLING == Address::BILLING) {
            $this->setBillingAddress($address);
        }

        if ($type & Address::SHIPPING == Address::SHIPPING) {
            $this->setShippingAddress($address);
        }

        return $address;
    }

    /**
     * @param Address $address
     *
     * @return Cart
     */
    public function setBillingAddress(Address $address)
    {
        $this->billing = $address;
        return $this;
    }

    /**
     *
     * @param Address $address
     *
     * @return Cart
     */
    public function setShippingAddress(Address $address)
    {
        $this->shipping = [$address];
        return $this;
    }

    /**
     * @param Item $item
     *
     * @return Cart
     */
    public function addItem(Item $item)
    {
        if ($this->items == null) {
            $this->items = [];
        }

        $this->items[] = $item;

        return $this;
    }

    /**
     * @param Address $shippingAddress
     *
     * @return Cart
     */
    public function addShippingAddress(Address $shippingAddress)
    {
        if ($this->shipping == null) {
            $this->shipping = [];
        }

        $this->shipping[] = $shippingAddress;

        return $this;
    }

    /**
     * @param string $name
     * @param string $email
     * @param string $cpf
     *
     * @return Consumer
     */
    public function consumer($name, $email, $cpf)
    {
        $consumer = new Consumer($name, $email, $cpf);

        $this->setConsumer($consumer);

        return $consumer;
    }

    /**
     *
     * @return Address
     */
    public function getBillingAddress()
    {
        return $this->billing;
    }

    /**
     *
     * @return Consumer
     */
    public function getConsumer()
    {
        return $this->consumer;
    }

    /**
     *
     * @param Consumer $consumer
     *
     * @return Cart
     */
    public function setConsumer(Consumer $consumer)
    {
        $this->consumer = $consumer;
        return $this;
    }

    /**
     *
     * @return Iata
     */
    public function getIata()
    {
        return $this->iata;
    }

    /**
     *
     * @param Flight $flight
     *
     * @return $this
     */
    public function setIata(Flight $flight)
    {
        $this->iata = new Iata();
        $this->iata->setFlight($flight);
        return $this;
    }

    /**
     *
     * @return Address
     */
    public function getShippingAddress()
    {
        return $this->shipping;
    }

    /**
     * @param Environment $environment
     *
     * @return Cart
     */
    public function setEnvironment(Environment $environment)
    {
        $this->environment = $environment;
        return $this;
    }

}


<?php

/*
 * This file is part of the DataLoaderPhp package.
 *
 * (c) Overblog <http://github.com/overblog/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Overblog\PromiseAdapter\Adapter;

use Overblog\PromiseAdapter\PromiseAdapterInterface;
use React\Promise\CancellablePromiseInterface;
use React\Promise\Deferred;
use React\Promise\FulfilledPromise;
use React\Promise\Promise;
use React\Promise\PromiseInterface;
use React\Promise\RejectedPromise;

class ReactPromiseAdapter implements PromiseAdapterInterface
{
    /**
     * {@inheritdoc}
     *
     * @return Promise
     */
    public function create(&$resolve = null, &$reject = null, callable $canceller = null)
    {
        $deferred = new Deferred($canceller);

        $reject = [$deferred, 'reject'];
        $resolve = [$deferred, 'resolve'];

        return $deferred->promise();
    }

    /**
     * {@inheritdoc}
     *
     * @return FulfilledPromise a full filed Promise
     */
    public function createFulfilled($promiseOrValue = null)
    {
        return \React\Promise\resolve($promiseOrValue);
    }

    /**
     * {@inheritdoc}
     *
     * @return RejectedPromise a rejected promise
     */
    public function createRejected($reason)
    {
        return \React\Promise\reject($reason);
    }

    /**
     * {@inheritdoc}
     *
     * @return Promise
     */
    public function createAll($promisesOrValues)
    {
        return \React\Promise\all($promisesOrValues);
    }

    /**
     * {@inheritdoc}
     */
    public function isPromise($value, $strict = false)
    {
        $isStrictPromise = $value instanceof PromiseInterface;

        if ($strict) {
            return $isStrictPromise;
        }

        return $isStrictPromise || is_callable([$value, 'then']);
    }

    /**
     * {@inheritdoc}
     */
    public function await($promise = null, $unwrap = false)
    {
        if (null === $promise) {
            return null;
        }
        $wait = true;
        $resolvedValue = null;
        $exception = null;
        if (!static::isPromise($promise)) {
            throw new \InvalidArgumentException(sprintf('The "%s" method must be called with a Promise ("then" method).', __METHOD__));
        }
        $promise->then(function ($values) use (&$resolvedValue, &$wait) {
            $resolvedValue = $values;
            $wait = false;
        }, function ($reason) use (&$exception, &$wait) {
            $exception = $reason;
            $wait = false;
        });

        if ($exception instanceof \Exception) {
            if (!$unwrap) {
                return $exception;
            }
            throw $exception;
        }

        return $resolvedValue;
    }

    /**
     * Cancel a promise
     *
     * @param CancellablePromiseInterface $promise
     */
    public function cancel($promise)
    {
        if (!$promise instanceof CancellablePromiseInterface) {
            throw new \InvalidArgumentException(sprintf('The "%s" method must be called with a compatible Promise.', __METHOD__));
        }
        $promise->cancel();
    }
}


<?php

/*
 * This file is part of the DataLoaderPhp package.
 *
 * (c) Overblog <http://github.com/overblog/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Overblog\PromiseAdapter\Adapter;

use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use Overblog\PromiseAdapter\PromiseAdapterInterface;

class GuzzleHttpPromiseAdapter implements PromiseAdapterInterface
{
    /**
     * {@inheritdoc}
     *
     * @return Promise
     */
    public function create(&$resolve = null, &$reject = null, callable $canceller = null)
    {
        $queue = \GuzzleHttp\Promise\queue();
        $promise = new Promise([$queue, 'run'], $canceller);

        $reject = [$promise, 'reject'];
        $resolve = [$promise, 'resolve'];

        return $promise;
    }

    /**
     * {@inheritdoc}
     *
     * @return FulfilledPromise a full filed Promise
     */
    public function createFulfilled($promiseOrValue = null)
    {
        $promise = \GuzzleHttp\Promise\promise_for($promiseOrValue);

        return $promise;
    }

    /**
     * {@inheritdoc}
     *
     * @return RejectedPromise a rejected promise
     */
    public function createRejected($reason)
    {
        $promise = \GuzzleHttp\Promise\rejection_for($reason);

        return $promise;
    }

    /**
     * {@inheritdoc}
     *
     * @return Promise
     */
    public function createAll($promisesOrValues)
    {
        $promise = empty($promisesOrValues) ? $this->createFulfilled($promisesOrValues) : \GuzzleHttp\Promise\all($promisesOrValues);

        return $promise;
    }

    /**
     * {@inheritdoc}
     */
    public function isPromise($value, $strict = false)
    {
        $isStrictPromise = $value instanceof PromiseInterface;

        if ($strict) {
            return $isStrictPromise;
        }

        return $isStrictPromise || is_callable([$value, 'then']);
    }

    /**
     * {@inheritdoc}
     */
    public function await($promise = null, $unwrap = false)
    {
        $resolvedValue = null;

        if (null !== $promise) {
            $exception = null;
            if (!static::isPromise($promise)) {
                throw new \InvalidArgumentException(sprintf('The "%s" method must be called with a Promise ("then" method).', __METHOD__));
            }

            /** @var Promise $promise */
            $promise->then(function ($values) use (&$resolvedValue) {
                $resolvedValue = $values;
            }, function ($reason) use (&$exception) {
                $exception = $reason;
            });
            \GuzzleHttp\Promise\queue()->run();

            if ($exception instanceof \Exception) {
                if (!$unwrap) {
                    return $exception;
                }
                throw $exception;
            }
        } else {
            \GuzzleHttp\Promise\queue()->run();
        }

        return $resolvedValue;
    }

    /**
     * Cancel a promise
     *
     * @param PromiseInterface $promise
     * {@inheritdoc}
     */
    public function cancel($promise)
    {
        if (!static::isPromise($promise, true)) {
            throw new \InvalidArgumentException(sprintf('The "%s" method must be called with a compatible Promise.', __METHOD__));
        }
        $promise->cancel();
    }
}


<?php

/*
 * This file is part of the DataLoaderPhp package.
 *
 * (c) Overblog <http://github.com/overblog/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Overblog\DataLoader;

use Overblog\PromiseAdapter\PromiseAdapterInterface;

class DataLoader implements DataLoaderInterface
{
    /**
     * @var callable
     */
    private $batchLoadFn;

    /**
     * @var Option
     */
    private $options;

    /**
     * @var CacheMap
     */
    private $promiseCache;

    /**
     * @var array
     */
    private $queue = [];

    /**
     * @var self[]
     */
    private static $instances = [];

    /**
     * @var PromiseAdapterInterface
     */
    private $promiseAdapter;

    public function __construct(callable $batchLoadFn, PromiseAdapterInterface $promiseFactory, Option $options = null)
    {
        $this->batchLoadFn = $batchLoadFn;
        $this->promiseAdapter = $promiseFactory;
        $this->options = $options ?: new Option();
        $this->promiseCache = $this->options->getCacheMap();
        self::$instances[] = $this;
    }

    /**
     * {@inheritdoc}
     */
    public function load($key)
    {
        $this->checkKey($key, __METHOD__);
        // Determine options
        $shouldBatch = $this->options->shouldBatch();
        $shouldCache = $this->options->shouldCache();
        $cacheKey = $this->getCacheKeyFromKey($key);

        // If caching and there is a cache-hit, return cached Promise.
        if ($shouldCache) {
            $cachedPromise = $this->promiseCache->get($cacheKey);
            if ($cachedPromise) {
                return $cachedPromise;
            }
        }

        // Otherwise, produce a new Promise for this value.
        $promise = $this->getPromiseAdapter()->create(
            $resolve,
            $reject,
            function () {
                // Cancel/abort any running operations like network connections, streams etc.

                throw new \RuntimeException('DataLoader destroyed before promise complete.');
            }
        );

        $this->queue[] = [
            'key' => $key,
            'resolve' => $resolve,
            'reject' => $reject,
            'promise' => $promise,
        ];

        // Determine if a dispatch of this queue should be scheduled.
        // A single dispatch should be scheduled per queue at the time when the
        // queue changes from "empty" to "full".
        if (count($this->queue) === 1) {
            if (!$shouldBatch) {
                // Otherwise dispatch the (queue of one) immediately.
                $this->dispatchQueue();
            }
        }
        // If caching, cache this promise.
        if ($shouldCache) {
            $this->promiseCache->set($cacheKey, $promise);
        }

        return $promise;
    }

    /**
     * {@inheritdoc}
     */
    public function loadMany($keys)
    {
        if (!is_array($keys) && !$keys instanceof \Traversable) {
            throw new \InvalidArgumentException(sprintf('The "%s" method must be called with Array<key> but got: %s.', __METHOD__, gettype($keys)));
        }
        return $this->getPromiseAdapter()->createAll(array_map(
            function ($key) {
                return $this->load($key);
            },
            $keys
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function clear($key)
    {
        $this->checkKey($key, __METHOD__);
        $cacheKey = $this->getCacheKeyFromKey($key);
        $this->promiseCache->clear($cacheKey);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function clearAll()
    {
        $this->promiseCache->clearAll();

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function prime($key, $value)
    {
        $this->checkKey($key, __METHOD__);

        $cacheKey = $this->getCacheKeyFromKey($key);

        // Only add the key if it does not already exist.
        if (!$this->promiseCache->has($cacheKey)) {
            // Cache a rejected promise if the value is an Error, in order to match
            // the behavior of load(key).
            $promise = $value instanceof \Exception ? $this->getPromiseAdapter()->createRejected($value) : $this->getPromiseAdapter()->createFulfilled($value);

            $this->promiseCache->set($cacheKey, $promise);
        }

        return $this;
    }

    public function __destruct()
    {
        if ($this->needProcess()) {
            foreach ($this->queue as $data) {
                try {
                    $this->getPromiseAdapter()->cancel($data['promise']);
                } catch (\Exception $e) {
                    // no need to do nothing if cancel failed
                }
            }
            $this->await();
        }
        foreach (self::$instances as $i => $instance) {
            if ($this !== $instance) {
                continue;
            }
            unset(self::$instances[$i]);
        }
    }

    protected function needProcess()
    {
        return count($this->queue) > 0;
    }

    protected function process()
    {
        if ($this->needProcess()) {
            $this->getPromiseAdapter()->await();
            $this->dispatchQueue();
        }
    }

    protected function getPromiseAdapter()
    {
        return $this->promiseAdapter;
    }

    /**
     * {@inheritdoc}
     */
    public static function await($promise = null, $unwrap = true)
    {
        self::awaitInstances();

        if (null === $promise) {
            return null;
        }

        if (is_callable([$promise, 'then'])) {
            $isPromiseCompleted = false;
            $resolvedValue = null;
            $rejectedReason = null;

            $promise->then(
                function ($value) use (&$isPromiseCompleted, &$resolvedValue) {
                    $isPromiseCompleted = true;
                    $resolvedValue = $value;
                },
                function ($reason) use (&$isPromiseCompleted, &$rejectedReason) {
                    $isPromiseCompleted = true;
                    $rejectedReason = $reason;
                }
            );

            //Promise is completed?
            if ($isPromiseCompleted) {
                // rejected ?
                if ($rejectedReason instanceof \Exception) {
                    if (!$unwrap) {
                        return $rejectedReason;
                    }
                    throw $rejectedReason;
                }

                return $resolvedValue;
            }
        }

        if (empty(self::$instances)) {
            throw new \RuntimeException('Found no active DataLoader instance.');
        }

        return self::$instances[0]->getPromiseAdapter()->await($promise, $unwrap);
    }

    private static function awaitInstances()
    {
        do {
            $wait = false;
            $dataLoaders = self::$instances;

            foreach ($dataLoaders as $dataLoader) {
                if (!$dataLoader || !$dataLoader->needProcess()) {
                    $wait |= false;
                    continue;
                }
                $wait = true;
                $dataLoader->process();
            }
        } while ($wait);
    }

    /**
     * @param $key
     *
     * @return mixed
     */
    protected function getCacheKeyFromKey($key)
    {
        $cacheKeyFn = $this->options->getCacheKeyFn();
        $cacheKey = $cacheKeyFn ? $cacheKeyFn($key) : $key;

        return $cacheKey;
    }

    /**
     * @param $key
     * @param $method
     */
    protected function checkKey($key, $method)
    {
        if (null === $key) {
            throw new \InvalidArgumentException(
                sprintf('The "%s" method must be called with a value, but got: %s.', $method, gettype($key))
            );
        }
    }

    /**
     * Given the current state of a Loader instance, perform a batch load
     * from its current queue.
     */
    private function dispatchQueue()
    {
        // Take the current loader queue, replacing it with an empty queue.
        $queue = $this->queue;
        $this->queue = [];
        $queueLength = count($queue);
        // If a maxBatchSize was provided and the queue is longer, then segment the
        // queue into multiple batches, otherwise treat the queue as a single batch.
        $maxBatchSize = $this->options->getMaxBatchSize();
        if ($maxBatchSize && $maxBatchSize > 0 && $maxBatchSize < $queueLength) {
            for ($i = 0; $i < $queueLength / $maxBatchSize; $i++) {
                $offset = $i * $maxBatchSize;
                $length = ($i + 1) * $maxBatchSize - $offset;

                $this->dispatchQueueBatch(array_slice($queue, $offset, $length));
            }
        } else {
            $this->dispatchQueueBatch($queue);
        }
    }

    private function dispatchQueueBatch(array $queue)
    {
        // Collect all keys to be loaded in this dispatch
        $keys = array_column($queue, 'key');

        // Call the provided batchLoadFn for this loader with the loader queue's keys.
        $batchLoadFn = $this->batchLoadFn;
        $batchPromise = $batchLoadFn($keys);

        // Assert the expected response from batchLoadFn
        if (!$batchPromise || !is_callable([$batchPromise, 'then'])) {
            $this->failedDispatch($queue, new \RuntimeException(
                'DataLoader must be constructed with a function which accepts ' .
                'Array<key> and returns Promise<Array<value>>, but the function did ' .
                sprintf('not return a Promise: %s.', gettype($batchPromise))
            ));

            return;
        }

        // Await the resolution of the call to batchLoadFn.
        $batchPromise->then(
            function ($values) use ($keys, $queue) {
                // Assert the expected resolution from batchLoadFn.
                if (!is_array($values) && !$values instanceof \Traversable) {
                    throw new \RuntimeException(
                        'DataLoader must be constructed with a function which accepts ' .
                        'Array<key> and returns Promise<Array<value>>, but the function did ' .
                        sprintf('not return a Promise of an Array: %s.', gettype($values))
                    );
                }
                if (count($values) !== count($keys)) {
                    throw new \RuntimeException(
                        'DataLoader must be constructed with a function which accepts ' .
                        'Array<key> and returns Promise<Array<value>>, but the function did ' .
                        'not return a Promise of an Array of the same length as the Array of keys.'
                    );
                }

                // Step through the values, resolving or rejecting each Promise in the
                // loaded queue.
                foreach ($queue as $index => $data) {
                    $value = $values[$index];
                    if ($value instanceof \Exception) {
                        $data['reject']($value);
                    } else {
                        $data['resolve']($value);
                    }
                };
            }
        )->then(null, function ($error) use ($queue) {
            $this->failedDispatch($queue, $error);
        });
    }

    /**
     * Do not cache individual loads if the entire batch dispatch fails,
     * but still reject each request so they do not hang.
     * @param array      $queue
     * @param \Exception $error
     */
    private function failedDispatch($queue, \Exception $error)
    {
        foreach ($queue as $index => $data) {
            $this->clear($data['key']);
            $data['reject']($error);
        }
    }
}


<?php

/*
 * This file is part of the DataLoaderPhp package.
 *
 * (c) Overblog <http://github.com/overblog/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Overblog\PromiseAdapter\Adapter;

use GraphQL\Deferred;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use Overblog\PromiseAdapter\PromiseAdapterInterface;

class WebonyxGraphQLSyncPromiseAdapter implements PromiseAdapterInterface
{
    /** @var callable[] */
    private $cancellers = [];

    /**
     * @var SyncPromiseAdapter
     */
    private $webonyxPromiseAdapter;

    public function __construct(SyncPromiseAdapter $webonyxPromiseAdapter = null)
    {
        $webonyxPromiseAdapter = $webonyxPromiseAdapter?:new SyncPromiseAdapter();
        $this->setWebonyxPromiseAdapter($webonyxPromiseAdapter);
    }

    /**
     * @return SyncPromiseAdapter
     */
    public function getWebonyxPromiseAdapter()
    {
        return $this->webonyxPromiseAdapter;
    }

    /**
     * @param SyncPromiseAdapter $webonyxPromiseAdapter
     */
    public function setWebonyxPromiseAdapter(SyncPromiseAdapter $webonyxPromiseAdapter)
    {
        $this->webonyxPromiseAdapter = $webonyxPromiseAdapter;
    }

    /**
     * {@inheritdoc}
     */
    public function create(&$resolve = null, &$reject = null, callable $canceller = null)
    {
        $promise = $this->webonyxPromiseAdapter->create(function ($res, $rej) use (&$resolve, &$reject) {
            $resolve = $res;
            $reject = $rej;
        });
        $this->cancellers[spl_object_hash($promise)] = $canceller;

        return $promise;
    }

    /**
     * {@inheritdoc}
     */
    public function createFulfilled($promiseOrValue = null)
    {
        return $this->getWebonyxPromiseAdapter()->createFulfilled($promiseOrValue);
    }

    /**
     * {@inheritdoc}
     */
    public function createRejected($reason)
    {
        return $this->getWebonyxPromiseAdapter()->createRejected($reason);
    }

    /**
     * {@inheritdoc}
     */
    public function createAll($promisesOrValues)
    {
        return $this->getWebonyxPromiseAdapter()->all($promisesOrValues);
    }

    /**
     * {@inheritdoc}
     */
    public function isPromise($value, $strict = false)
    {
        if ($value instanceof Promise) {
            $value = $value->adoptedPromise;
        }
        $isStrictPromise = $value instanceof SyncPromise;
        if ($strict) {
            return $isStrictPromise;
        }

        return $isStrictPromise || is_callable([$value, 'then']);
    }

    /**
     * {@inheritdoc}
     */
    public function await($promise = null, $unwrap = false)
    {
        if (null === $promise) {
            Deferred::runQueue();
            SyncPromise::runQueue();
            $this->cancellers = [];
            return null;
        }
        $promiseAdapter = $this->getWebonyxPromiseAdapter();

        $resolvedValue = null;
        $exception = null;
        if (!$this->isPromise($promise)) {
            throw new \InvalidArgumentException(sprintf('The "%s" method must be called with a Promise ("then" method).', __METHOD__));
        }

        try {
            $resolvedValue = $promiseAdapter->wait($promise);
        } catch (\Exception $reason) {
            $exception = $reason;
        }
        if ($exception instanceof \Exception) {
            if (!$unwrap) {
                return $exception;
            }
            throw $exception;
        }

        $hash = spl_object_hash($promise);
        unset($this->cancellers[$hash]);
        return $resolvedValue;
    }

    /**
     * {@inheritdoc}
     */
    public function cancel($promise)
    {
        $hash = spl_object_hash($promise);
        if (!$this->isPromise($promise) || !isset($this->cancellers[$hash])) {
            throw new \InvalidArgumentException(sprintf('The "%s" method must be called with a compatible Promise.', __METHOD__));
        }
        $canceller = $this->cancellers[$hash];
        unset($this->cancellers[$hash]);
        $adoptedPromise = $promise;
        if ($promise instanceof Promise) {
            $adoptedPromise = $promise->adoptedPromise;
        }
        try {
            $canceller([$adoptedPromise, 'resolve'], [$adoptedPromise, 'reject']);
        } catch (\Exception $reason) {
            $adoptedPromise->reject($reason);
        }
    }
}


<?php

namespace aymanrb\UnstructuredTextParser;

use DirectoryIterator;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class TextParser
{
    use LoggerAwareTrait;

    /** @var DirectoryIterator; Templates Directory Iterator */
    protected $directoryIterator;

    /**
     * @param string $templatesDir ; The path to the template files directory
     * @param LoggerInterface $logger ; A logger instance implementing the PSR-3 Logger interface
     */
    public function __construct($templatesDir, LoggerInterface $logger = null)
    {
        if (empty($logger)) {
            $logger = new NullLogger();
        }

        $this->setLogger($logger);
        $this->createTemplatesDirIterator($templatesDir);
    }

    /**
     * The call for action method, this is the parse job initiator
     *
     * @param string $text ; The text provided by the user for parsing
     * @param boolean $findMatchingTemplate ; A boolean to enable the similarity match against templates before parsing (slower)
     * @return array The matched data array or null on unmatched text
     *
     */
    public function parseText($text, $findMatchingTemplate = false)
    {
        $this->logger->info(sprintf('Parsing: %s', $text));

        $text = $this->prepareText($text);
        $matchedTemplates = $this->getTemplates($text, $findMatchingTemplate);

        foreach ($matchedTemplates as $templatePath => $templateContent) {
            $this->logger->debug(sprintf('Parsing against template: %s', $templatePath));

            $templatePattern = $this->prepareTemplate($templateContent);
            $extractedData = $this->extractData($text, $templatePattern);

            if ($extractedData) {
                $this->logger->info(sprintf('Data extracted: %s', json_encode($extractedData)));

                return $extractedData;
            }
        }

        return null;
    }

    /**
     * Returns array of available template patterns or performs a similarity match (slower) to return best match template
     *
     * @param string $text ; The text provided by the user for parsing
     * @param boolean $findMatchingTemplate ; A boolean to enable the similarity match against templates before parsing
     * @return array
     */
    protected function getTemplates($text, $findMatchingTemplate)
    {
        if ($findMatchingTemplate) {
            return $this->findTemplate($text);
        }

        $templates = [];
        foreach ($this->directoryIterator as $fileInfo) {
            if (!is_file($fileInfo->getPathname())) {
                continue;
            }

            $templates[$fileInfo->getPathname()] = file_get_contents($fileInfo->getPathname());
        }

        return $templates;
    }

    /**
     * Sets the class property $templatesDirectoryPath
     *
     * @param string $templatesDir ; The path to the template files directory
     * @throws \Exception
     */

    protected function createTemplatesDirIterator($templatesDir)
    {
        if (empty($templatesDir) || !is_dir($templatesDir)) {
            throw new \Exception('Invalid templates directory provided');
        }

        $this->directoryIterator = new DirectoryIterator(rtrim($templatesDir, '/'));
    }

    /**
     * Prepares the provided text for parsing by escaping known characters and removing exccess whitespaces
     *
     * @param string $txt ; The text provided by the user for parsing
     * @return string; The prepared clean text
     *
     */
    protected function prepareText($txt)
    {
        //Remove all multiple whitespaces and replace it with single space
        $txt = preg_replace('/\s+/', ' ', $txt);

        return trim($txt);
    }

    /**
     * Prepares the matched template text for parsing by escaping known characters and removing excess whitespaces
     *
     * @param string $templateTxt ; The matched template contents
     * @return string; The prepared clean template pattern
     *
     */
    protected function prepareTemplate($templateTxt)
    {
        $patterns = [
            '/\\\{%(.*)%\\\}/U', // 1 Replace all {%Var%}...
            '/\s+/',             // 2 Replace all white-spaces...
        ];

        $replacements = [
            '(?<$1>.*)',         // 1 ...with (?<Var>.*)
            ' ',                 // 2 ...with a single space
        ];

        $templateTxt = preg_replace($patterns, $replacements, preg_quote($templateTxt, '/'));

        return trim($templateTxt);
    }

    /**
     * Extracts the named variables values from within the text based on the provided template
     *
     * @param string $text ; The prepared text provided by the user for parsing
     * @param string $template ; The template regex pattern from the matched template
     * @return array|bool; The matched data array or false on unmatched text
     *
     */
    protected function extractData($text, $template)
    {
        //Extract the text based on the provided template using REGEX
        preg_match('/' . $template . '/s', $text, $matches);

        //Extract only the named parameters from the matched regex array
        $keys = array_filter(array_keys($matches), 'is_string');
        $matches = array_intersect_key($matches, array_flip($keys));

        if (!empty($matches)) {
            return $this->cleanExtractedData($matches);
        }

        return false;
    }

    /**
     * Removes unwanted stuff from the data array like html tags and extra spaces
     *
     * @param mixed $matches ; Array with matched strings
     * @return array; The clean data array
     *
     */
    protected function cleanExtractedData($matches)
    {
        return array_map([$this, 'cleanElement'], $matches);
    }

    /**
     * A callback method to remove unwanted stuff from the extracted data element
     *
     * @param string $value ; The extracted text from the matched element
     * @return string; clean/stripped text
     *
     */
    protected function cleanElement($value)
    {
        return trim(strip_tags($value));
    }

    /**
     * Iterates through the templates directory to find the closest template pattern that matches the provided text
     *
     * @param string $text ; The text provided by the user for parsing
     * @return array; The matched template contents with its path as a key or empty array if none matched
     *
     */
    protected function findTemplate($text)
    {
        $matchedTemplate = [];
        $maxMatch = -1;

        foreach ($this->directoryIterator as $fileInfo) {
            $templateContent = file_get_contents($fileInfo->getPathname());

            similar_text($text, $templateContent,
                $matchPercentage); //Compare template against text to decide on similarity percentage

            if ($matchPercentage > $maxMatch) {
                $this->logger->debug(sprintf('Template "%s" is a best match for now', $fileInfo->getPathname()));

                $maxMatch = $matchPercentage;
                $matchedTemplate = [$fileInfo->getPathname() => $templateContent];
            }
        }

        return $matchedTemplate;
    }
}


<?php
define ("SERIAL_DEVICE_NOTSET", 0);
define ("SERIAL_DEVICE_SET", 1);
define ("SERIAL_DEVICE_OPENED", 2);

/**
 * Serial port control class
 *
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTIES !
 * USE IT AT YOUR OWN RISKS !
 *
 * @author Rémy Sanchez <remy.sanchez@hyperthese.net>
 * @author Rizwan Kassim <rizwank@geekymedia.com>
 * @thanks Aurélien Derouineau for finding how to open serial ports with windows
 * @thanks Alec Avedisyan for help and testing with reading
 * @thanks Jim Wright for OSX cleanup/fixes.
 * @copyright under GPL 2 licence
 */
class PhpSerial
{
    public $_device = null;
    public $_winDevice = null;
    public $_dHandle = null;
    public $_dState = SERIAL_DEVICE_NOTSET;
    public $_buffer = "";
    public $_os = "";

    /**
     * This var says if buffer should be flushed by sendMessage (true) or
     * manually (false)
     *
     * @var bool
     */
    public $autoFlush = true;

    /**
     * Constructor. Perform some checks about the OS and setserial
     *
     * @return PhpSerial
     */
    public function PhpSerial()
    {
        setlocale(LC_ALL, "en_US");

        $sysName = php_uname();

        if (substr($sysName, 0, 5) === "Linux") {
            $this->_os = "linux";

            if ($this->_exec("stty") === 0) {
                register_shutdown_function(array($this, "deviceClose"));
            } else {
                trigger_error(
                    "No stty availible, unable to run.",
                    E_USER_ERROR
                );
            }
        } elseif (substr($sysName, 0, 6) === "Darwin") {
            $this->_os = "osx";
            register_shutdown_function(array($this, "deviceClose"));
        } elseif (substr($sysName, 0, 7) === "Windows") {
            $this->_os = "windows";
            register_shutdown_function(array($this, "deviceClose"));
        } else {
            trigger_error("Host OS is neither osx, linux nor windows, unable " .
                          "to run.", E_USER_ERROR);
            exit();
        }
    }

    //
    // OPEN/CLOSE DEVICE SECTION -- {START}
    //

    /**
     * Device set function : used to set the device name/address.
     * -> linux : use the device address, like /dev/ttyS0
     * -> osx : use the device address, like /dev/tty.serial
     * -> windows : use the COMxx device name, like COM1 (can also be used
     *     with linux)
     *
     * @param  string $device the name of the device to be used
     * @return bool
     */
    public function deviceSet($device)
    {
        if ($this->_dState !== SERIAL_DEVICE_OPENED) {
            if ($this->_os === "linux") {
                if (preg_match("@^COM(\\d+):?$@i", $device, $matches)) {
                    $device = "/dev/ttyS" . ($matches[1] - 1);
                }

                if ($this->_exec("stty -F " . $device) === 0) {
                    $this->_device = $device;
                    $this->_dState = SERIAL_DEVICE_SET;

                    return true;
                }
            } elseif ($this->_os === "osx") {
                if ($this->_exec("stty -f " . $device) === 0) {
                    $this->_device = $device;
                    $this->_dState = SERIAL_DEVICE_SET;

                    return true;
                }
            } elseif ($this->_os === "windows") {
                if (preg_match("@^COM(\\d+):?$@i", $device, $matches)
                        and $this->_exec(
                            exec("mode " . $device . " xon=on BAUD=9600")
                        ) === 0
                ) {
                    $this->_winDevice = "COM" . $matches[1];
                    $this->_device = "\\.com" . $matches[1];
                    $this->_dState = SERIAL_DEVICE_SET;

                    return true;
                }
            }

            trigger_error("Specified serial port is not valid", E_USER_WARNING);

            return false;
        } else {
            trigger_error("You must close your device before to set an other " .
                          "one", E_USER_WARNING);

            return false;
        }
    }

    /**
     * Opens the device for reading and/or writing.
     *
     * @param  string $mode Opening mode : same parameter as fopen()
     * @return bool
     */
    public function deviceOpen($mode = "r+b")
    {
        if ($this->_dState === SERIAL_DEVICE_OPENED) {
            trigger_error("The device is already opened", E_USER_NOTICE);

            return true;
        }

        if ($this->_dState === SERIAL_DEVICE_NOTSET) {
            trigger_error(
                "The device must be set before to be open",
                E_USER_WARNING
            );

            return false;
        }

        if (!preg_match("@^[raw]\\+?b?$@", $mode)) {
            trigger_error(
                "Invalid opening mode : ".$mode.". Use fopen() modes.",
                E_USER_WARNING
            );

            return false;
        }

        $this->_dHandle = @fopen($this->_device, $mode);

        if ($this->_dHandle !== false) {
            stream_set_blocking($this->_dHandle, 0);
            $this->_dState = SERIAL_DEVICE_OPENED;

            return true;
        }

        $this->_dHandle = null;
        trigger_error("Unable to open the device", E_USER_WARNING);

        return false;
    }

    /**
     * Closes the device
     *
     * @return bool
     */
    public function deviceClose()
    {
        if ($this->_dState !== SERIAL_DEVICE_OPENED) {
            return true;
        }

        if (fclose($this->_dHandle)) {
            $this->_dHandle = null;
            $this->_dState = SERIAL_DEVICE_SET;

            return true;
        }

        trigger_error("Unable to close the device", E_USER_ERROR);

        return false;
    }

    //
    // OPEN/CLOSE DEVICE SECTION -- {STOP}
    //

    //
    // CONFIGURE SECTION -- {START}
    //

    /**
     * Configure the Baud Rate
     * Possible rates : 110, 150, 300, 600, 1200, 2400, 4800, 9600, 38400,
     * 57600 and 115200.
     *
     * @param  int  $rate the rate to set the port in
     * @return bool
     */
    public function confBaudRate($rate)
    {
        if ($this->_dState !== SERIAL_DEVICE_SET) {
            trigger_error("Unable to set the baud rate : the device is " .
                          "either not set or opened", E_USER_WARNING);

            return false;
        }

        $validBauds = array (
            110    => 11,
            150    => 15,
            300    => 30,
            600    => 60,
            1200   => 12,
            2400   => 24,
            4800   => 48,
            9600   => 96,
            19200  => 19,
            38400  => 38400,
            57600  => 57600,
            115200 => 115200
        );

        if (isset($validBauds[$rate])) {
            if ($this->_os === "linux") {
                $ret = $this->_exec(
                    "stty -F " . $this->_device . " " . (int) $rate,
                    $out
                );
            } elseif ($this->_os === "osx") {
                $ret = $this->_exec(
                    "stty -f " . $this->_device . " " . (int) $rate,
                    $out
                );
            } elseif ($this->_os === "windows") {
                $ret = $this->_exec(
                    "mode " . $this->_winDevice . " BAUD=" . $validBauds[$rate],
                    $out
                );
            } else {
                return false;
            }

            if ($ret !== 0) {
                trigger_error(
                    "Unable to set baud rate: " . $out[1],
                    E_USER_WARNING
                );

                return false;
            }

            return true;
        } else {
            return false;
        }
    }

    /**
     * Configure parity.
     * Modes : odd, even, none
     *
     * @param  string $parity one of the modes
     * @return bool
     */
    public function confParity($parity)
    {
        if ($this->_dState !== SERIAL_DEVICE_SET) {
            trigger_error(
                "Unable to set parity : the device is either not set or opened",
                E_USER_WARNING
            );

            return false;
        }

        $args = array(
            "none" => "-parenb",
            "odd"  => "parenb parodd",
            "even" => "parenb -parodd",
        );

        if (!isset($args[$parity])) {
            trigger_error("Parity mode not supported", E_USER_WARNING);

            return false;
        }

        if ($this->_os === "linux") {
            $ret = $this->_exec(
                "stty -F " . $this->_device . " " . $args[$parity],
                $out
            );
        } elseif ($this->_os === "osx") {
            $ret = $this->_exec(
                "stty -f " . $this->_device . " " . $args[$parity],
                $out
            );
        } else {
            $ret = $this->_exec(
                "mode " . $this->_winDevice . " PARITY=" . $parity{0},
                $out
            );
        }

        if ($ret === 0) {
            return true;
        }

        trigger_error("Unable to set parity : " . $out[1], E_USER_WARNING);

        return false;
    }

    /**
     * Sets the length of a character.
     *
     * @param  int  $int length of a character (5 <= length <= 8)
     * @return bool
     */
    public function confCharacterLength($int)
    {
        if ($this->_dState !== SERIAL_DEVICE_SET) {
            trigger_error("Unable to set length of a character : the device " .
                          "is either not set or opened", E_USER_WARNING);

            return false;
        }

        $int = (int) $int;
        if ($int < 5) {
            $int = 5;
        } elseif ($int > 8) {
            $int = 8;
        }

        if ($this->_os === "linux") {
            $ret = $this->_exec(
                "stty -F " . $this->_device . " cs" . $int,
                $out
            );
        } elseif ($this->_os === "osx") {
            $ret = $this->_exec(
                "stty -f " . $this->_device . " cs" . $int,
                $out
            );
        } else {
            $ret = $this->_exec(
                "mode " . $this->_winDevice . " DATA=" . $int,
                $out
            );
        }

        if ($ret === 0) {
            return true;
        }

        trigger_error(
            "Unable to set character length : " .$out[1],
            E_USER_WARNING
        );

        return false;
    }

    /**
     * Sets the length of stop bits.
     *
     * @param  float $length the length of a stop bit. It must be either 1,
     *                       1.5 or 2. 1.5 is not supported under linux and on
     *                       some computers.
     * @return bool
     */
    public function confStopBits($length)
    {
        if ($this->_dState !== SERIAL_DEVICE_SET) {
            trigger_error("Unable to set the length of a stop bit : the " .
                          "device is either not set or opened", E_USER_WARNING);

            return false;
        }

        if ($length != 1
                and $length != 2
                and $length != 1.5
                and !($length == 1.5 and $this->_os === "linux")
        ) {
            trigger_error(
                "Specified stop bit length is invalid",
                E_USER_WARNING
            );

            return false;
        }

        if ($this->_os === "linux") {
            $ret = $this->_exec(
                "stty -F " . $this->_device . " " .
                    (($length == 1) ? "-" : "") . "cstopb",
                $out
            );
        } elseif ($this->_os === "osx") {
            $ret = $this->_exec(
                "stty -f " . $this->_device . " " .
                    (($length == 1) ? "-" : "") . "cstopb",
                $out
            );
        } else {
            $ret = $this->_exec(
                "mode " . $this->_winDevice . " STOP=" . $length,
                $out
            );
        }

        if ($ret === 0) {
            return true;
        }

        trigger_error(
            "Unable to set stop bit length : " . $out[1],
            E_USER_WARNING
        );

        return false;
    }

    /**
     * Configures the flow control
     *
     * @param  string $mode Set the flow control mode. Availible modes :
     *                      -> "none" : no flow control
     *                      -> "rts/cts" : use RTS/CTS handshaking
     *                      -> "xon/xoff" : use XON/XOFF protocol
     * @return bool
     */
    public function confFlowControl($mode)
    {
        if ($this->_dState !== SERIAL_DEVICE_SET) {
            trigger_error("Unable to set flow control mode : the device is " .
                          "either not set or opened", E_USER_WARNING);

            return false;
        }

        $linuxModes = array(
            "none"     => "clocal -crtscts -ixon -ixoff",
            "rts/cts"  => "-clocal crtscts -ixon -ixoff",
            "xon/xoff" => "-clocal -crtscts ixon ixoff"
        );
        $windowsModes = array(
            "none"     => "xon=off octs=off rts=on",
            "rts/cts"  => "xon=off octs=on rts=hs",
            "xon/xoff" => "xon=on octs=off rts=on",
        );

        if ($mode !== "none" and $mode !== "rts/cts" and $mode !== "xon/xoff") {
            trigger_error("Invalid flow control mode specified", E_USER_ERROR);

            return false;
        }

        if ($this->_os === "linux") {
            $ret = $this->_exec(
                "stty -F " . $this->_device . " " . $linuxModes[$mode],
                $out
            );
        } elseif ($this->_os === "osx") {
            $ret = $this->_exec(
                "stty -f " . $this->_device . " " . $linuxModes[$mode],
                $out
            );
        } else {
            $ret = $this->_exec(
                "mode " . $this->_winDevice . " " . $windowsModes[$mode],
                $out
            );
        }

        if ($ret === 0) {
            return true;
        } else {
            trigger_error(
                "Unable to set flow control : " . $out[1],
                E_USER_ERROR
            );

            return false;
        }
    }

    /**
     * Sets a setserial parameter (cf man setserial)
     * NO MORE USEFUL !
     * 	-> No longer supported
     * 	-> Only use it if you need it
     *
     * @param  string $param parameter name
     * @param  string $arg   parameter value
     * @return bool
     */
    public function setSetserialFlag($param, $arg = "")
    {
        if (!$this->_ckOpened()) {
            return false;
        }

        $return = exec(
            "setserial " . $this->_device . " " . $param . " " . $arg . " 2>&1"
        );

        if ($return{0} === "I") {
            trigger_error("setserial: Invalid flag", E_USER_WARNING);

            return false;
        } elseif ($return{0} === "/") {
            trigger_error("setserial: Error with device file", E_USER_WARNING);

            return false;
        } else {
            return true;
        }
    }

    //
    // CONFIGURE SECTION -- {STOP}
    //

    //
    // I/O SECTION -- {START}
    //

    /**
     * Sends a string to the device
     *
     * @param string $str          string to be sent to the device
     * @param float  $waitForReply time to wait for the reply (in seconds)
     */
    public function sendMessage($str, $waitForReply = 0.1)
    {
        $this->_buffer .= $str;

        if ($this->autoFlush === true) {
            $this->serialflush();
        }

        usleep((int) ($waitForReply * 1000000));
    }

    /**
     * Reads the port until no new datas are availible, then return the content.
     *
     * @param int $count Number of characters to be read (will stop before
     *                   if less characters are in the buffer)
     * @return string
     */
    public function readPort($count = 0)
    {
        if ($this->_dState !== SERIAL_DEVICE_OPENED) {
            trigger_error("Device must be opened to read it", E_USER_WARNING);

            return false;
        }

        if ($this->_os === "linux" || $this->_os === "osx") {
            // Behavior in OSX isn't to wait for new data to recover, but just
            // grabs what's there!
            // Doesn't always work perfectly for me in OSX
            $content = ""; $i = 0;

            if ($count !== 0) {
                do {
                    if ($i > $count) {
                        $content .= fread($this->_dHandle, ($count - $i));
                    } else {
                        $content .= fread($this->_dHandle, 128);
                    }
                } while (($i += 128) === strlen($content));
            } else {
                do {
                    $content .= fread($this->_dHandle, 128);
                } while (($i += 128) === strlen($content));
            }

            return $content;
        } elseif ($this->_os === "windows") {
            // Windows port reading procedures still buggy
            $content = ""; $i = 0;

            if ($count !== 0) {
                do {
                    if ($i > $count) {
                        $content .= fread($this->_dHandle, ($count - $i));
                    } else {
                        $content .= fread($this->_dHandle, 128);
                    }
                } while (($i += 128) === strlen($content));
            } else {
                do {
                    $content .= fread($this->_dHandle, 128);
                } while (($i += 128) === strlen($content));
            }

            return $content;
        }

        return false;
    }

    /**
     * Flushes the output buffer
     * Renamed from flush for osx compat. issues
     *
     * @return bool
     */
    public function serialflush()
    {
        if (!$this->_ckOpened()) {
            return false;
        }

        if (fwrite($this->_dHandle, $this->_buffer) !== false) {
            $this->_buffer = "";

            return true;
        } else {
            $this->_buffer = "";
            trigger_error("Error while sending message", E_USER_WARNING);

            return false;
        }
    }

    //
    // I/O SECTION -- {STOP}
    //

    //
    // INTERNAL TOOLKIT -- {START}
    //

    public function _ckOpened()
    {
        if ($this->_dState !== SERIAL_DEVICE_OPENED) {
            trigger_error("Device must be opened", E_USER_WARNING);

            return false;
        }

        return true;
    }

    public function _ckClosed()
    {
        if ($this->_dState === SERIAL_DEVICE_OPENED) {
            trigger_error("Device must be closed", E_USER_WARNING);

            return false;
        }

        return true;
    }

    public function _exec($cmd, &$out = null)
    {
        $desc = array(
            1 => array("pipe", "w"),
            2 => array("pipe", "w")
        );

        $proc = proc_open($cmd, $desc, $pipes);

        $ret = stream_get_contents($pipes[1]);
        $err = stream_get_contents($pipes[2]);

        fclose($pipes[1]);
        fclose($pipes[2]);

        $retVal = proc_close($proc);

        if (func_num_args() == 2) $out = array($ret, $err);
        return $retVal;
    }

    //
    // INTERNAL TOOLKIT -- {STOP}
    //
}


<?php
namespace jianyan\easywechat;

use Yii;
use EasyWeChat\Factory;
use yii\base\Component;

/**
 * Class Wechat
 *
 * @package jianyan\easywechat
 *
 * @property \EasyWeChat\OfficialAccount\Application $app 微信SDK实例
 * @property \EasyWeChat\Payment\Application $payment 微信支付SDK实例
 * @property \EasyWeChat\MiniProgram\Application $miniProgram 微信小程序实例
 * @property \EasyWeChat\OpenPlatform\Application $openPlatform 微信开放平台(第三方平台)实例
 * @property \EasyWeChat\Work\Application $work 企业微信实例
 * @property \EasyWeChat\OpenWork\Application $openWork 企业微信开放平台实例
 *
 * @property bool $isWechat 检查客户端是否是微信浏览器
 * @property WechatUser $user 获取微信身份信息
 */
class Wechat extends Component
{
    /**
     * user identity class params
     * @var array
     */
    public $userOptions = [];

    /**
     * wechat user info will be stored in session under this key
     * @var string
     */
    public $sessionParam = '_wechatUser';

    /**
     * returnUrl param stored in session
     * @var string
     */
    public $returnUrlParam = '_wechatReturnUrl';

    /**
     * 微信SDK
     *
     * @var Factory
     */
    private static $_app;

    /**
     * 支付 SKD
     *
     * @var Factory
     */
    private static $_payment;

    /**
     * 小程序 SKD
     *
     * @var Factory
     */
    private static $_miniProgram;

    /**
     * 第三方开放平台 SKD
     *
     * @var Factory
     */
    private static $_openPlatform;

    /**
     * 企业微信 SKD
     *
     * @var Factory
     */
    private static $_work;

    /**
     * 企业微信开放平台 SKD
     *
     * @var Factory
     */
    private static $_openWork;

    /**
     * @var WechatUser
     */
    private static $_user;

    /**
     * @return $this|Yii\web\Response
     * @throws \yii\base\InvalidConfigException
     */
    public function authorizeRequired()
    {
        if(Yii::$app->request->get('code'))
        {
            // callback and authorize
            return $this->authorize($this->app->oauth->user());
        }
        else
        {
            // redirect to wechat authorize page
            $this->setReturnUrl(Yii::$app->request->getUrl());
            return Yii::$app->response->redirect($this->app->oauth->redirect()->getTargetUrl());
        }
    }

    /**
     * @param \Overtrue\Socialite\User $user
     * @return yii\web\Response
     */
    public function authorize(\Overtrue\Socialite\User $user)
    {
        Yii::$app->session->set($this->sessionParam, $user->toJSON());
        return Yii::$app->response->redirect($this->getReturnUrl());
    }

    /**
     * check if current user authorized
     * @return bool
     */
    public function isAuthorized()
    {
        $hasSession = Yii::$app->session->has($this->sessionParam);
        $sessionVal = Yii::$app->session->get($this->sessionParam);
        return ($hasSession && !empty($sessionVal));
    }

    /**
     * @param string|array $url
     */
    public function setReturnUrl($url)
    {
        Yii::$app->session->set($this->returnUrlParam, $url);
    }

    /**
     * @param null $defaultUrl
     * @return mixed|null|string
     */
    public function getReturnUrl($defaultUrl = null)
    {
        $url = Yii::$app->session->get($this->returnUrlParam, $defaultUrl);
        if (is_array($url))
        {
            if (isset($url[0]))
            {
                return Yii::$app->getUrlManager()->createUrl($url);
            }
            else
            {
                $url = null;
            }
        }

        return $url === null ? Yii::$app->getHomeUrl() : $url;
    }

    /**
     * 获取 EasyWeChat 微信实例
     *
     * @return Factory|\EasyWeChat\OfficialAccount\Application
     */
    public function getApp()
    {
        if (!self::$_app instanceof Factory)
        {
            self::$_app = Factory::officialAccount(Yii::$app->params['wechatConfig']);
        }

        return self::$_app;
    }

    /**
     * 获取 EasyWeChat 微信支付实例
     *
     * @return Factory
     */
    public function getPayment()
    {
        if (!self::$_payment instanceof Factory)
        {
            self::$_payment = Factory::payment(Yii::$app->params['wechatPaymentConfig']);
        }

        return self::$_payment;
    }

    /**
     * 获取 EasyWeChat 微信小程序实例
     *
     * @return Factory
     */
    public function getMiniProgram()
    {
        if (!self::$_miniProgram instanceof Factory)
        {
            self::$_miniProgram = Factory::miniProgram(Yii::$app->params['wechatMiniProgramConfig']);
        }

        return self::$_miniProgram;
    }

    /**
     * 获取 EasyWeChat 微信第三方开放平台实例
     *
     * @return Factory
     */
    public function getOpenPlatform()
    {
        if (!self::$_openPlatform instanceof Factory)
        {
            self::$_openPlatform = Factory::openPlatform(Yii::$app->params['wechatOpenPlatformConfig']);
        }

        return self::$_openPlatform;
    }

    /**
     * 获取 EasyWeChat 企业微信实例
     *
     * @return Factory
     */
    public function getWork()
    {
        if (!self::$_work instanceof Factory)
        {
            self::$_work = Factory::work(Yii::$app->params['wechatWorkConfig']);
        }

        return self::$_work;
    }

    /**
     * 获取 EasyWeChat 企业微信开放平台实例
     *
     * @return Factory
     */
    public function getOpenWork()
    {
        if (!self::$_openWork instanceof Factory)
        {
            self::$_openWork = Factory::openWork(Yii::$app->params['wechatOpenWorkConfig']);
        }

        return self::$_openWork;
    }

    /**
     * 获取微信身份信息
     *
     * @return WechatUser
     */
    public function getUser()
    {
        if (!$this->isAuthorized())
        {
            return new WechatUser();
        }

        if (! self::$_user instanceof WechatUser)
        {
            $userInfo = Yii::$app->session->get($this->sessionParam);
            $config = $userInfo ? json_decode($userInfo, true) : [];
            self::$_user = new WechatUser($config);
        }
        return self::$_user;
    }

    /**
     * overwrite the getter in order to be compatible with this component
     * @param $name
     * @return mixed
     * @throws \Exception
     */
    public function __get($name)
    {
        try
        {
            return parent::__get($name);
        }
        catch (\Exception $e)
        {
            if($this->getApp()->$name)
            {
                return $this->app->$name;
            }
            else
            {
                throw $e->getPrevious();
            }
        }
    }

    /**
     * check if client is wechat
     * @return bool
     */
    public function getIsWechat()
    {
        return strpos($_SERVER["HTTP_USER_AGENT"], "MicroMessenger") !== false;
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bundle\AclBundle\DependencyInjection;

use Symfony\Component\Console\Application;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;

/**
 * AclExtension.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class AclExtension extends Extension
{
    /**
     * {@inheritdoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $mainConfig = $this->getConfiguration($configs, $container);
        $config = $this->processConfiguration($mainConfig, $configs);

        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('acl.xml');

        if (class_exists(Application::class)) {
            $loader->load('console.xml');
        }

        if (isset($config['cache']['id'])) {
            $container->setAlias('security.acl.cache', $config['cache']['id']);
        }
        $container->getDefinition('security.acl.voter.basic_permissions')->addArgument($config['voter']['allow_if_object_identity_unavailable']);

        // custom ACL provider
        if (isset($config['provider'])) {
            $container->setAlias('security.acl.provider', $config['provider']);

            return;
        }

        // no provider configured
        $loader->load('acl_dbal.xml');

        if (null !== $config['connection']) {
            $container->setAlias('security.acl.dbal.connection', sprintf('doctrine.dbal.%s_connection', $config['connection']));
        }

        $container
            ->getDefinition('security.acl.dbal.schema_listener')
            ->addTag('doctrine.event_listener', array(
                'connection' => $config['connection'],
                'event' => 'postGenerateSchema',
                'lazy' => true,
            ))
        ;

        $container->getDefinition('security.acl.cache.doctrine')->addArgument($config['cache']['prefix']);

        $container->setParameter('security.acl.dbal.class_table_name', $config['tables']['class']);
        $container->setParameter('security.acl.dbal.entry_table_name', $config['tables']['entry']);
        $container->setParameter('security.acl.dbal.oid_table_name', $config['tables']['object_identity']);
        $container->setParameter('security.acl.dbal.oid_ancestors_table_name', $config['tables']['object_identity_ancestors']);
        $container->setParameter('security.acl.dbal.sid_table_name', $config['tables']['security_identity']);
    }

    /**
     * {@inheritdoc}
     */
    public function getNamespace()
    {
        return 'http://symfony.com/schema/dic/acl';
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bundle\AclBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * This class contains the configuration information.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class Configuration implements ConfigurationInterface
{
    /**
     * Generates the configuration tree builder.
     *
     * @return TreeBuilder The tree builder
     */
    public function getConfigTreeBuilder()
    {
        $tb = new TreeBuilder();
        $rootNode = $tb->root('acl');

        $rootNode
            ->children()
                ->scalarNode('connection')
                    ->defaultNull()
                    ->info('any name configured in doctrine.dbal section')
                ->end()
                ->arrayNode('cache')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->scalarNode('id')->end()
                        ->scalarNode('prefix')->defaultValue('sf_acl_')->end()
                    ->end()
                ->end()
                ->scalarNode('provider')->end()
                ->arrayNode('tables')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->scalarNode('class')->defaultValue('acl_classes')->end()
                        ->scalarNode('entry')->defaultValue('acl_entries')->end()
                        ->scalarNode('object_identity')->defaultValue('acl_object_identities')->end()
                        ->scalarNode('object_identity_ancestors')->defaultValue('acl_object_identity_ancestors')->end()
                        ->scalarNode('security_identity')->defaultValue('acl_security_identities')->end()
                    ->end()
                ->end()
                ->arrayNode('voter')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->booleanNode('allow_if_object_identity_unavailable')->defaultTrue()->end()
                    ->end()
                ->end()
            ->end()
        ;

        return $tb;
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bundle\AclBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;

/**
 * Sets ACL for objects.
 *
 * @author Kévin Dunglas <kevin@les-tilleuls.coop>
 */
final class SetAclCommand extends Command
{
    protected static $defaultName = 'acl:set';

    private $provider;

    public function __construct(MutableAclProviderInterface $provider)
    {
        parent::__construct();

        $this->provider = $provider;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setDescription('Sets ACL for objects')
            ->setHelp(<<<EOF
The <info>%command.name%</info> command sets ACL.
The ACL system must have been initialized with the <info>acl:init</info> command.

To set <comment>VIEW</comment> and <comment>EDIT</comment> permissions for the user <comment>kevin</comment> on the instance of
<comment>Acme\MyClass</comment> having the identifier <comment>42</comment>:

  <info>php %command.full_name% --user=Symfony/Component/Security/Core/User/User:kevin VIEW EDIT Acme/MyClass:42</info>

Note that you can use <comment>/</comment> instead of <comment>\\ </comment>for the namespace delimiter to avoid any
problem.

To set permissions for a role, use the <info>--role</info> option:

  <info>php %command.full_name% --role=ROLE_USER VIEW Acme/MyClass:1936</info>

To set permissions at the class scope, use the <info>--class-scope</info> option:

  <info>php %command.full_name% --class-scope --user=Symfony/Component/Security/Core/User/User:anne OWNER Acme/MyClass:42</info>

EOF
            )
            ->addArgument('arguments', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of permissions and object identities (class name and ID separated by a column)')
            ->addOption('user', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A list of security identities')
            ->addOption('role', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'A list of roles')
            ->addOption('class-scope', null, InputOption::VALUE_NONE, 'Use class-scope entries')
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Parse arguments
        $objectIdentities = array();
        $maskBuilder = new MaskBuilder();
        foreach ($input->getArgument('arguments') as $argument) {
            $data = explode(':', $argument, 2);

            if (count($data) > 1) {
                $objectIdentities[] = new ObjectIdentity($data[1], strtr($data[0], '/', '\\'));
            } else {
                $maskBuilder->add($data[0]);
            }
        }

        // Build permissions mask
        $mask = $maskBuilder->get();

        $userOption = $input->getOption('user');
        $roleOption = $input->getOption('role');
        $classScopeOption = $input->getOption('class-scope');

        if (empty($userOption) && empty($roleOption)) {
            throw new \InvalidArgumentException('A Role or a User must be specified.');
        }

        // Create security identities
        $securityIdentities = array();

        if ($userOption) {
            foreach ($userOption as $user) {
                $data = explode(':', $user, 2);

                if (1 === count($data)) {
                    throw new \InvalidArgumentException('The user must follow the format "Acme/MyUser:username".');
                }

                $securityIdentities[] = new UserSecurityIdentity($data[1], strtr($data[0], '/', '\\'));
            }
        }

        if ($roleOption) {
            foreach ($roleOption as $role) {
                $securityIdentities[] = new RoleSecurityIdentity($role);
            }
        }

        // Sets ACL
        foreach ($objectIdentities as $objectIdentity) {
            // Creates a new ACL if it does not already exist
            try {
                $this->provider->createAcl($objectIdentity);
            } catch (AclAlreadyExistsException $e) {
            }

            $acl = $this->provider->findAcl($objectIdentity, $securityIdentities);

            foreach ($securityIdentities as $securityIdentity) {
                if ($classScopeOption) {
                    $acl->insertClassAce($securityIdentity, $mask);
                } else {
                    $acl->insertObjectAce($securityIdentity, $mask);
                }
            }

            $this->provider->updateAcl($acl);
        }
    }
}


<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Bundle\AclBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Security\Acl\Dbal\Schema;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\SchemaException;

/**
 * Creates the tables required by the ACL system.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
final class InitAclCommand extends Command
{
    protected static $defaultName = 'acl:init';

    private $connection;
    private $schema;

    public function __construct(Connection $connection, Schema $schema)
    {
        parent::__construct();

        $this->connection = $connection;
        $this->schema = $schema;
    }

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setDescription('Creates ACL tables in the database')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command creates ACL tables in the database.

  <info>php %command.full_name%</info>

The name of the DBAL connection must be configured in the <info>acl.connection</info> variable of your acl configuration file.

  <info>acl:
    connection: default</info>
EOF
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            $this->schema->addToSchema($this->connection->getSchemaManager()->createSchema());
        } catch (SchemaException $e) {
            $output->writeln('Aborting: '.$e->getMessage());

            return 1;
        }

        foreach ($this->schema->toSql($this->connection->getDatabasePlatform()) as $sql) {
            $this->connection->exec($sql);
        }

        $output->writeln('ACL tables have been initialized successfully.');
    }
}


<?php

namespace Clue\Redis\Server\Business;

use Clue\Redis\Server\Storage;
use Exception;
use InvalidArgumentException;
use Clue\Redis\Server\Client;

class Keys
{
    private $storage;

    public function __construct(Storage $storage = null)
    {
        if ($storage === null) {
            $storage = new Storage();
        }
        $this->storage = $storage;
    }

    public function keys($pattern)
    {
        $ret = array();

        foreach ($this->storage->getAllKeys() as $key) {
            if(fnmatch($pattern, $key)) {
                $ret []= $key;
            }
        }

        return $ret;
    }

    public function randomkey()
    {
        return $this->storage->getRandomKey();
    }

    public function sort($key)
    {
        if ($this->storage->hasKey($key)) {
            $list = iterator_to_array($this->storage->getOrCreateList($key), false);
        } else {
            // no need to sort, but validate arguments in order to be consistent with reference implementation
            $list = array();
        }

        $by     = null;
        $offset = null;
        $count  = null;
        $get    = array();
        $desc   = false;
        $sort   = SORT_NUMERIC;
        $store  = null;

        $args = func_get_args();
        $next = function() use (&$args, &$i) {
            if (!isset($args[$i + 1])) {
                throw new Exception('ERR syntax error');
            }
            return $args[++$i];
        };
        for ($i = 1, $l = count($args); $i < $l; ++$i) {
            $arg = strtoupper($args[$i]);

            if ($arg === 'BY') {
                $by = $next();
            } elseif ($arg === 'LIMIT') {
                $offset = $this->coerceInteger($next());
                $count  = $this->coerceInteger($next());
            } elseif ($arg === 'GET') {
                $get []= $next();
            } elseif ($arg === 'ASC' || $arg === 'DESC') {
                $desc = ($arg === 'DESC');
            } elseif ($arg === 'ALPHA') {
                $sort = SORT_STRING;
            } elseif ($arg === 'STORE') {
                $store = $next();
            } else {
                throw new Exception('ERR syntax error');
            }
        }

        if ($sort === SORT_NUMERIC) {
            $cmp = function($a, $b) {
                $fa = (float)$a;
                $fb = (float)$b;
                if ((string)$fa !== $a || (string)$fb !== $b) {
                    throw new Exception('ERR One or more scores can\'t be converted into double');
                }
                return ($fa < $fb) ? -1 : (($fa > $fb) ? 1 : 0);
            };
        } else {
            $cmp = 'strcmp';
        }

        if ($by !== null) {
            $pos = strpos($by, '*');
            if ($pos === false) {
                $cmp = null;
            } else {
                $cmp = 'strcmp';

                $lookup = array();
                foreach (array_unique($list) as $v) {
                    $key = str_replace('*', $v, $by);
                    $lookup[$v] = $this->storage->getStringOrNull($key);
                    if ($lookup[$v] === null) {
                        $lookup[$v] = $v;
                    }
                }

                $cmp = function($a, $b) use ($cmp, $lookup){
                    return $cmp($lookup[$a], $lookup[$b]);
                };
            }
        }

        if ($cmp !== null) {
            usort($list, $cmp);
        }

        if ($desc) {
            $list = array_reverse($list);
        }

        if ($offset !== null) {
            $list = array_slice($list, $offset, $count, false);
        }

        if ($get) {
            $storage = $this->storage;
            $lookup = function($key, $pattern) use ($storage) {
                if ($pattern === '#') {
                    return $key;
                }

                $l = str_replace('*', $key, $pattern);

                static $cached = array();

                if (!array_key_exists($l, $cached)) {
                    $cached[$l] = $storage->getStringOrNull($l);
                }

                return $cached[$l];
            };
            $keys = $list;
            $list = array();

            foreach ($keys as $key) {
                foreach ($get as $pattern) {
                    $list []= $lookup($key, $pattern);
                }
            }
        }

        if ($store !== null) {
            $this->storage->unsetKey($store);

            if (!$list) {
                return 0;
            }

            $sl = $this->storage->getOrCreateList($store);

            foreach ($list as $value) {
                $sl->push($value);
            }

            return $sl->count();
        }

        return $list;
    }

    public function persist($key)
    {
        if ($this->storage->hasKey($key)) {
            $timeout = $this->storage->getTimeout($key);

            if ($timeout !== null) {
                $this->storage->setTimeout($key, null);
                return true;
            }
        }
        return false;
    }

    public function expire($key, $seconds)
    {
        return $this->pexpireat($key, (int)(1000 * (microtime(true) + $this->coerceInteger($seconds))));
    }

    public function expireat($key, $timestamp)
    {
        return $this->pexpireat($key, 1000 * $this->coerceInteger($timestamp));
    }

    public function pexpire($key, $milliseconds)
    {
        return $this->pexpireat($key, (int)(1000 * microtime(true)) + $this->coerceInteger($milliseconds));
    }

    public function pexpireat($key, $millisecondTimestamp)
    {
        $millisecondTimestamp = $this->coerceInteger($millisecondTimestamp);

        if (!$this->storage->hasKey($key)) {
            return false;
        }
        $this->storage->setTimeout($key, $millisecondTimestamp / 1000);

        return true;
    }

    public function ttl($key)
    {
        $pttl = $this->pttl($key);
        if ($pttl > 0) {
            $pttl = (int)($pttl / 1000);
        }
        return $pttl;
    }

    public function pttl($key)
    {
        if (!$this->storage->hasKey($key)) {
            return -2;
        }

        $timeout = $this->storage->getTimeout($key);
        if ($timeout === null) {
            return -1;
        }

        $milliseconds = 1000 * ($timeout - microtime(true));
        if ($milliseconds < 0) {
            $milliseconds = 0;
        }

        return (int)$milliseconds;
    }

    public function del($key0)
    {
        $n = 0;

        foreach (func_get_args() as $key) {
            if ($this->storage->hasKey($key)) {
                $this->storage->unsetKey($key);
                ++$n;
            }
        }

        return $n;
    }

    public function exists($key)
    {
        return $this->storage->hasKey($key);
    }

    // StatusReply
    public function rename($key, $newkey)
    {
        if ($key === $newkey) {
            throw new Exception('ERR source and destination objects are the same');
        } elseif (!$this->storage->hasKey($key)) {
            throw new Exception('ERR no such key');
        }

        if ($this->storage->hasKey($newkey)) {
            $this->del($newkey);
        }

        $this->storage->rename($key, $newkey);

        return true;
    }

    public function renamenx($key, $newkey)
    {
        if ($key === $newkey) {
            throw new Exception('ERR source and destination objects are the same');
        } elseif (!$this->storage->hasKey($key)) {
            throw new Exception('ERR no such key');
        }

        if ($this->storage->hasKey($newkey)) {
            return false;
        }

        $this->storage->rename($key, $newkey);

        return true;
    }

    // StatusReply
    public function type($key)
    {
        if (!$this->storage->hasKey($key)) {
            return 'none';
        }

        $value = $this->storage->get($key);
        if (is_string($value)) {
            return 'string';
        } elseif ($value instanceof \SplDoublyLinkedList) {
            return 'list';
        } else {
            throw new UnexpectedValueException('Unknown datatype encountered');
        }
    }

    private function coerceInteger($value)
    {
        $int = (int)$value;
        if ((string)$int !== (string)$value) {
            throw new Exception('ERR value is not an integer or out of range');
        }
        return $int;
    }

    public function setClient(Client $client)
    {
        $this->storage = $client->getDatabase();
    }
}


<?php

namespace Clue\Redis\Server\Business;

use InvalidArgumentException;
use OutOfBoundsException;
use Clue\Redis\Server\Server as ServerInstance;
use Clue\Redis\Server\Client;

class Server
{
    private $server;
    private $client = null;

    public function __construct(ServerInstance $server)
    {
        $this->server = $server;
    }

    // StatusReply
    public function client($subcommand)
    {
        $n = func_num_args();
        $subcommand = strtolower($subcommand);

        if ($subcommand === 'list' && $n === 1) {
            $ret = '';
            foreach ($this->getAllClients() as $client) {
                $ret .= $client->getDescription() . "\n";
            }
            return $ret;
        } elseif ($subcommand === 'kill' && $n === 2) {
            $this->getClientByIp(func_get_arg(1))->end();
            return true;
        } elseif ($subcommand === 'getname' && $n === 1) {
            return $this->getClient()->getName();
        } elseif ($subcommand === 'setname' && $n === 2) {
            $this->getClient()->setName(func_get_arg(1));

            return true;
        }

        throw new InvalidArgumentException('ERR Syntax error, try CLIENT (LIST | KILL ip:port | GETNAME | SETNAME connection-name)');
    }

    // StatusReply
    public function config($subcommand)
    {
        $n = func_num_args();
        $subcommand = strtolower($subcommand);

        if ($subcommand === 'get') {
            if ($n !== 2) {
                throw new InvalidArgumentException('ERR Wrong number of arguments for CONFIG get');
            }
            $pattern = func_get_arg(1);
            $ret = array();
            foreach ($this->getConfig() as $name => $value) {
                if (fnmatch($pattern, $name)) {
                    $ret []= $name;
                    $ret []= $value;
                }
            }
            return $ret;
        } elseif ($subcommand === 'set') {
            if ($n !== 3) {
                throw new InvalidArgumentException('ERR Wrong number of arguments for CONFIG set');
            }
            $this->getConfig()->set(func_get_arg(1), func_get_arg(2));
            return true;
        }

        throw new InvalidArgumentException('ERR CONFIG subcommand must be one of GET, SET');
    }

    public function dbsize()
    {
        return $this->getDatabase()->count();
    }

    // StatusReply
    public function flushdb()
    {
        $this->getDatabase()->reset();

        return true;
    }

    // StatusReply
    public function flushall()
    {
        foreach ($this->getDatabases() as $database) {
            $database->reset();
        }

        return true;
    }

    public function shutdown()
    {
        // save/nosave doesn't matter

        // this command disconnects all clients and closes the server socket,
        // so there's no need to return a reply

        foreach ($this->getAllClients() as $client) {
            $client->close();
        }

        $this->getServer()->close();
    }

    public function time()
    {
        $time = array_reverse(explode(' ', microtime(false)));
        $time[1] = trim($time[1], ".0");

        return $time;
    }

    private function getAllClients()
    {
        return $this->getServer()->getClients();
    }

    public function setClient(Client $client)
    {
        $this->client = $client;
    }

    private function getClient()
    {
        if ($this->client === null) {
            throw new UnexpectedValueException('Invalid state');
        }
        return $this->client;
    }

    private function getClientByIp($ip)
    {
        foreach ($this->getAllClients() as $client) {
            if ($client->getRemoteAddress() === $ip) {
                return $client;
            }
        }
        throw new OutOfBoundsException('ERR No such client');
    }

    private function getConfig()
    {
        return $this->server->getConfig();
    }

    private function getDatabases()
    {
        return $this->getServer()->getDatabases();
    }

    private function getDatabase()
    {
        return $this->getClient()->getDatabase();
    }

    private function getServer()
    {
        return $this->server;
    }
}


<?php

namespace Clue\Redis\Server\Business;

use Clue\Redis\Server\Server;
use OutOfBoundsException;
use UnexpectedValueException;
use Clue\Redis\Server\Client;
use Clue\Redis\Server\AuthInvoker;

class Connection
{
    private $server;
    private $client = null;

    public function __construct(Server $server)
    {
        $this->server = $server;
    }

    // StatusReply
    public function auth($password)
    {
        if ($this->getConfig()->get('requirepass') === '') {
            throw new UnexpectedValueException('ERR Client sent AUTH, but no password is set');
        }

        if ($this->getConfig()->get('requirepass') !== $password) {
            throw new UnexpectedValueException('ERR invalid password');
        }

        $business = $this->getClient()->getBusiness();
        if ($business instanceof AuthInvoker) {
            $this->getClient()->setBusiness($business->getSuccessfulInvoker());
        }

        return true;
    }

    public function x_echo($message)
    {
        return $message;
    }

    // StatusReply
    public function ping()
    {
        return 'PONG';
    }

    // StatusReply
    public function select($index)
    {
        $this->getClient()->setDatabase($this->getDatabaseById($index));

        return true;
    }

    // StatusReply
    public function quit()
    {
        // this command will end the connection and therefor not send any more
        // messages (not even the return code). For this reason we have to send
        // an OK message manually.
        $this->getClient()->write($this->getSerializer()->getStatusMessage('OK'));
        $this->getClient()->end();

        return true;
    }

    public function setClient(Client $client)
    {
        $this->client = $client;
    }

    private function getClient()
    {
        if ($this->client === null) {
            throw new UnexpectedValueException('Invalid state');
        }
        return $this->client;
    }

    private function getSerializer()
    {
        return $this->getClient()->getBusiness()->getSerializer();
    }

    private function getServer()
    {
        return $this->server;
    }

    private function getDatabases()
    {
        return $this->getServer()->getDatabases();
    }

    private function getDatabaseById($id)
    {
        foreach ($this->getDatabases() as $database) {
            if ($database->getId() === $id) {
                return $database;
            }
        }
        throw new OutOfBoundsException('ERR invalid DB index');
    }

    private function getConfig()
    {
        return $this->server->getConfig();
    }
}


<?php

namespace Clue\Redis\Server\Business;

use Clue\Redis\Server\Storage;
use Exception;
use InvalidArgumentException;
use Clue\Redis\Server\Client;

class Strings
{
    private $storage;

    public function __construct(Storage $storage = null)
    {
        if ($storage === null) {
            $storage = new Storage();
        }
        $this->storage = $storage;
    }

    public function append($key, $value)
    {
        $string =& $this->storage->getStringRef($key);
        $string .= $value;

        return strlen($string);
    }

    public function get($key)
    {
        return $this->storage->getStringOrNull($key);
    }

    // StatusReply
    public function set($key, $value)
    {
        if (func_num_args() > 2) {
            $args = func_get_args();
            array_shift($args);
            array_shift($args);

            $px = null;
            $ex = null;
            $xx = false;
            $nx = false;

            for ($i = 0, $n = count($args); $i < $n; ++$i) {
                $arg = strtoupper($args[$i]);

                if ($arg === 'XX') {
                    $xx = true;
                } elseif ($arg === 'NX') {
                    $nx = true;
                } elseif ($arg === 'EX' || $arg === 'PX') {
                    if (!isset($args[$i + 1])) {
                        throw new InvalidArgumentException('ERR syntax error');
                    }
                    $num = $this->coerceInteger($args[++$i]);
                    if ($num <= 0) {
                        throw new InvalidArgumentException('ERR invalid expire time in SETEX');
                    }

                    if ($arg === 'EX') {
                        $ex = $num;
                    } else {
                        $px = $num;
                    }
                } else {
                    throw new InvalidArgumentException('ERR syntax error');
                }
            }

            if ($nx && $this->storage->hasKey($key)) {
                return null;
            }

            if ($xx && !$this->storage->hasKey($key)) {
                return null;
            }

            if ($ex !== null) {
                $px += $ex * 1000;
            }

            if ($px !== null) {
                return $this->psetex($key, $px, $value);
            }
        }

        $this->storage->setString($key, $value);

        return true;
    }

    // StatusReply
    public function setex($key, $seconds, $value)
    {
        return $this->psetex($key, $this->coerceInteger($seconds) * 1000, $value);
    }

    // StatusReply
    public function psetex($key, $milliseconds, $value)
    {
        $milliseconds = $this->coerceInteger($milliseconds);

        $this->storage->setString($key, $value);
        $this->storage->setTimeout($key, microtime(true) + ($milliseconds / 1000));

        return true;
    }

    public function setnx($key, $value)
    {
        if ($this->storage->hasKey($key)) {
            return false;
        }

        $this->storage->setString($key, $value);

        return true;
    }

    public function incr($key)
    {
        return $this->incrby($key, 1);
    }

    public function incrby($key, $increment)
    {
        $increment = $this->coerceInteger($increment);

        $value = $this->storage->getIntegerOrNull($key);
        $value += $increment;

        $this->storage->setString($key, $value);

        return $value;
    }

    public function decr($key)
    {
        return $this->incrby($key, -1);
    }

    public function decrby($key, $decrement)
    {
        return $this->incrby($key, -$this->coerceInteger($decrement));
    }

    public function getset($key, $value)
    {
        $old = $this->storage->getStringOrNull($key);
        $this->storage->setString($key, $value);

        return $old;
    }

    public function getrange($key, $start, $end)
    {
        $start = $this->coerceInteger($start);
        $end   = $this->coerceInteger($end);

        $string = $this->storage->getStringOrNull($key);

        if ($end > 0) {
            $end = $end - $start + 1;
        } elseif ($end < 0) {
            if ($start < 0) {
                $end = $end - $start + 1;
            } else {
                $end += 1;
                if ($end === 0) {
                    return $string;
                }
            }
        }

        return (string)substr($string, $start, $end);
    }

    public function setrange($key, $offset, $value)
    {
        $offset = $this->coerceInteger($offset);

        $string =& $this->storage->getStringRef($key);
        $slen = strlen($string);

        $post = '';

        if ($slen < $offset) {
            $string .= str_repeat("\0", $offset - $slen);
        } else {
            $post = (string)substr($string, $offset + strlen($value));
            $string = substr($string, 0, $offset);
        }

        $string .= $value . $post;

        return strlen($string);
    }

    public function mget($key0)
    {
        $keys = func_get_args();
        $ret  = array();

        foreach ($keys as $key) {
            try {
                $ret []= $this->storage->getStringOrNull($key);
            }
            catch (Exception $ignore) {
                $ret []= null;
            }
        }

        return $ret;
    }

    // StatusReply
    public function mset($key0, $value0)
    {
        $n = func_num_args();
        if ($n & 1) {
            throw new Exception('ERR wrong number of arguments for \'mset\' command');
        }

        $args = func_get_args();
        for ($i = 0; $i < $n; $i += 2) {
            $this->storage->setString($args[$i], $args[$i + 1]);
        }

        return true;
    }

    public function msetnx($key0, $value0)
    {
        for ($i = 0, $n = func_num_args(); $i < $n; $i += 2) {
            if ($this->storage->hasKey(func_get_arg($i))) {
                return false;
            }
        }

        call_user_func_array(array($this, 'mset'), func_get_args());

        return true;
    }

    public function strlen($key)
    {
        return strlen($this->storage->getStringOrNull($key));
    }

    public function setClient(Client $client)
    {
        $this->storage = $client->getDatabase();
    }

    private function coerceInteger($value)
    {
        $int = (int)$value;
        if ((string)$int !== (string)$value) {
            throw new Exception('ERR value is not an integer or out of range');
        }
        return $int;
    }
}