<?php
declare(strict_types=1);
namespace Viserio\Component\Console\Command;

use Closure;
use Invoker\Exception\InvocationException as InvokerInvocationException;
use Invoker\Reflection\CallableReflection;
use ReflectionMethod;
use Symfony\Component\Console\Input\Input;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Viserio\Component\Console\Application;
use Viserio\Component\Contract\Console\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Console\Exception\InvocationException;
use Viserio\Component\Contract\Console\Exception\LogicException;
use Viserio\Component\Support\Invoker;
use Viserio\Component\Support\Str;

/**
 * Code in this class it taken from silly.
 *
 * See the original here: https://github.com/mnapoli/silly/blob/master/src/Application.php
 *
 * @author Matthieu Napoli https://github.com/mnapoli
 * @copyright Copyright (c) Matthieu Napoli
 */
final class CommandResolver
{
    /**
     * Invoker instance.
     *
     * @var \Viserio\Component\Support\Invoker
     */
    private $invoker;

    /**
     * Application instance.
     *
     * @var \Viserio\Component\Console\Application
     */
    private $console;

    /**
     * Create a new command resolver instance.
     *
     * @param \Viserio\Component\Support\Invoker     $invoker
     * @param \Viserio\Component\Console\Application $console
     */
    public function __construct(Invoker $invoker, Application $console)
    {
        $this->invoker = $invoker;
        $this->console = $console;
    }

    /**
     * Resolve a command from expression.
     *
     * @param string                $expression Defines the arguments and options of the command
     * @param array|callable|string $callable   Called when the command is called.
     *                                          When using a container, this can be a "pseudo-callable"
     *                                          i.e. the name of the container entry to invoke.
     * @param array                 $aliases    an array of aliases for the command
     *
     * @throws \Viserio\Component\Contract\Console\Exception\InvocationException
     * @throws \Viserio\Component\Contract\Console\Exception\InvalidArgumentException
     *
     * @return \Viserio\Component\Console\Command\StringCommand
     */
    public function resolve(string $expression, $callable, array $aliases = []): StringCommand
    {
        $this->assertCallableIsValid($callable);

        $commandFunction = function (InputInterface $input, OutputInterface $output) use ($callable) {
            $parameters = \array_merge(
                [
                    // Injection by parameter name
                    'input'  => $input,
                    'output' => $output,
                    // Injections by type-hint
                    InputInterface::class  => $input,
                    OutputInterface::class => $output,
                    Input::class           => $input,
                    Output::class          => $output,
                    SymfonyStyle::class    => new SymfonyStyle($input, $output),
                ],
                $input->getArguments(),
                $input->getOptions()
            );

            if ($callable instanceof Closure) {
                $callable = $callable->bindTo($this->console, $this->console);
            }

            try {
                return $this->invoker->addResolver(new HyphenatedInputResolver())->call($callable, $parameters);
            } catch (InvokerInvocationException $exception) {
                throw new InvocationException(
                    \sprintf(
                        "Impossible to call the '%s' command: %s",
                        $input->getFirstArgument(),
                        $exception->getMessage()
                    ),
                    0,
                    $exception
                );
            }
        };

        $command = self::createCommand($expression, $commandFunction);
        $command->setAliases($aliases);
        $command->defaults($this->defaultsViaReflection($command, $callable));

        return $command;
    }

    /**
     * Create a new command.
     *
     * @param string   $expression
     * @param callable $callable
     *
     * @throws \Symfony\Component\Console\Exception\LogicException
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     * @throws \Viserio\Component\Contract\Console\Exception\InvalidCommandExpression
     *
     * @return \Viserio\Component\Console\Command\StringCommand
     */
    private static function createCommand(string $expression, callable $callable): StringCommand
    {
        $result = ExpressionParser::parse($expression);

        $command = new StringCommand($result['name']);
        $command->getDefinition()->addArguments($result['arguments']);
        $command->getDefinition()->addOptions($result['options']);
        $command->setCode($callable);

        return $command;
    }

    /**
     * Reflect default values from callable.
     *
     * @param \Viserio\Component\Console\Command\StringCommand $command
     * @param callable|string                                  $callable
     *
     * @throws \Invoker\Exception\NotCallableException
     *
     * @return array
     */
    private function defaultsViaReflection(StringCommand $command, $callable): array
    {
        if (! \is_callable($callable)) {
            return [];
        }

        $function   = CallableReflection::create($callable);
        $definition = $command->getDefinition();
        $defaults   = [];

        foreach ($function->getParameters() as $parameter) {
            if (! $parameter->isDefaultValueAvailable()) {
                continue;
            }

            $parameterName      = $parameter->name;
            $hyphenatedCaseName = Str::snake($parameterName, '-');

            if ($definition->hasArgument($hyphenatedCaseName) || $definition->hasOption($hyphenatedCaseName)) {
                $parameterName = $hyphenatedCaseName;
            }

            if (! $definition->hasArgument($parameterName) && ! $definition->hasOption($parameterName)) {
                continue;
            }

            $defaults[$parameterName] = $parameter->getDefaultValue();
        }

        return $defaults;
    }

    /**
     * Check if callable is valid.
     *
     * @param mixed $callable
     *
     * @throws \ReflectionException
     * @throws \Viserio\Component\Contract\Console\Exception\InvalidArgumentException
     *
     * @return void
     */
    private function assertCallableIsValid($callable): void
    {
        try {
            $this->console->getContainer();
        } catch (LogicException $e) {
            if ($this->isStaticCallToNonStaticMethod($callable)) {
                [$class, $method] = $callable;

                $message = "['{$class}', '{$method}'] is not a callable because '{$method}' is a static method.";
                $message .= " Either use [new {$class}(), '{$method}'] or configure a dependency injection container that supports autowiring.";

                throw new InvalidArgumentException($message);
            }
        }
    }

    /**
     * Check if the callable represents a static call to a non-static method.
     *
     * @param mixed $callable
     *
     * @throws \ReflectionException
     *
     * @return bool
     */
    private function isStaticCallToNonStaticMethod($callable): bool
    {
        if (\is_array($callable) && \is_string($callable[0])) {
            [$class, $method] = $callable;

            $reflection = new ReflectionMethod($class, $method);

            return ! $reflection->isStatic();
        }

        return false;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Cookie\Provider;

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Contract\Cookie\QueueingFactory as JarContract;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\OptionsResolver\RequiresMandatoryOptions as RequiresMandatoryOptionsContract;
use Viserio\Component\Cookie\CookieJar;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

class CookieServiceProvider implements
    ServiceProviderInterface,
    RequiresComponentConfigContract,
    ProvidesDefaultOptionsContract,
    RequiresMandatoryOptionsContract
{
    use OptionsResolverTrait;

    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            JarContract::class => [self::class, 'createCookieJar'],
            'cookie'           => static function (ContainerInterface $container) {
                return $container->get(JarContract::class);
            },
            CookieJar::class => static function (ContainerInterface $container) {
                return $container->get(JarContract::class);
            },
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDimensions(): array
    {
        return ['viserio', 'cookie'];
    }

    /**
     * {@inheritdoc}
     */
    public static function getMandatoryOptions(): array
    {
        return ['path', 'domain'];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'secure' => true,
        ];
    }

    /**
     * Create a new CookieJar instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Contract\Cookie\QueueingFactory
     */
    public static function createCookieJar(ContainerInterface $container): JarContract
    {
        $options = self::resolveOptions($container->get('config'));

        return (new CookieJar())->setDefaultPathAndDomain(
            $options['path'],
            $options['domain'],
            $options['secure']
        );
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Session;

use ParagonIE\Halite\HiddenString;
use ParagonIE\Halite\Symmetric\Crypto;
use ParagonIE\Halite\Symmetric\EncryptionKey;
use SessionHandlerInterface as SessionHandlerContract;

class EncryptedStore extends Store
{
    /**
     * Encryption key instance.
     *
     * @var \ParagonIE\Halite\Symmetric\EncryptionKey
     */
    private $key;

    /**
     * Create a new session instance.
     *
     * @param string                                    $name
     * @param \SessionHandlerInterface                  $handler
     * @param \ParagonIE\Halite\Symmetric\EncryptionKey $key
     */
    public function __construct(string $name, SessionHandlerContract $handler, EncryptionKey $key)
    {
        parent::__construct($name, $handler);

        $this->key = $key;
    }

    /**
     * Hide this from var_dump(), etc.
     *
     * @return array
     */
    public function __debugInfo(): array
    {
        return [
            'key' => 'private',
        ];
    }

    /**
     * {@inheritdoc}
     */
    protected function prepareForReadFromHandler($data): array
    {
        $hiddenString = Crypto::decrypt($data, $this->key);

        if ($decryptedValue = $hiddenString->getString()) {
            $sessionData = \json_decode($decryptedValue, true);

            \sodium_memzero($decryptedValue);

            if ($sessionData === null) {
                return [];
            }

            return $sessionData;
        }

        return [];
    }

    /**
     * {@inheritdoc}
     */
    protected function prepareForWriteToHandler(string $data): string
    {
        return Crypto::encrypt(new HiddenString($data), $this->key);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Parser\Dumper;

use DOMDocument;
use DOMElement;
use DOMException;
use Viserio\Component\Contract\Parser\Dumper as DumperContract;
use Viserio\Component\Contract\Parser\Exception\DumpException;

class XmlDumper implements DumperContract
{
    /**
     * {@inheritdoc}
     *
     *    array[]
     *        ['root']                           string|array A optional option; String if you like to change the root name or
     *                                                                           array for more options.
     *            ['rootElementName']            string       A optional option; The name of the root element
     *            ['_attributes or @attributes'] array
     *                ['key']                    string
     *        ['version']                        string       A optional option;
     *        ['encoding']                       string       A optional option; to set the file encoding
     *        ['key']                            string|array
     *            ['_attributes or @attributes'] array        A optional option;
     *                ['key']                    string       A optional option;
     *            ['_value or @value']           string       A optional option;
     *            ['_cdata or @cdata']           string       A optional option;
     */
    public function dump(array $data): string
    {
        try {
            $document = new DOMDocument($data['version'] ?? '1.0', $data['encoding'] ?? '');

            if (isset($data['version'])) {
                unset($data['version']);
            }

            if (isset($data['encoding'])) {
                unset($data['encoding']);
            }

            if (\count($data) !== 0 && self::isArrayAllKeySequential($data)) {
                throw new DOMException('Invalid Character Error.');
            }

            $root = $this->createRootElement($document, $data['root'] ?? '');

            if (isset($data['root'])) {
                unset($data['root']);
            }

            $document->appendChild($root);

            $this->convertElement($document, $root, $data);

            return $document->saveXML();
        } catch (DOMException $exception) {
            throw new DumpException(
                $exception->getMessage(),
                $exception->getCode(),
                $exception
            );
        }
    }

    /**
     * Parse individual element.
     *
     * @param \DOMDocument         $document
     * @param \DOMElement|\DOMNode $element
     * @param array|string         $value
     *
     * @throws \DOMException
     *
     * @return void
     */
    private function convertElement(DOMDocument $document, $element, $value): void
    {
        $sequential = self::isArrayAllKeySequential($value);

        if (! \is_array($value)) {
            $element->nodeValue = \htmlspecialchars((string) $value);

            return;
        }

        foreach ($value as $key => $data) {
            if (! $sequential) {
                if (($key === '_attributes') || ($key === '@attributes')) {
                    foreach ($data as $attrKey => $attrVal) {
                        $element->setAttribute($attrKey, (string) $attrVal);
                    }
                } elseif ((($key === '_value') || ($key === '@value')) && \is_string($data)) {
                    $element->nodeValue = \htmlspecialchars($data);
                } elseif ((($key === '_cdata') || ($key === '@cdata')) && \is_string($data)) {
                    $element->appendChild($document->createCDATASection($data));
                } else {
                    if (! \is_string($key)) {
                        throw new DOMException('Invalid Character Error.');
                    }

                    $this->addNode($document, $element, $key, $data);
                }
            } elseif (\is_array($data)) {
                $this->addCollectionNode($document, $element, $data);
            } else {
                $this->addSequentialNode($element, $data);
            }
        }
    }

    /**
     * Add node.
     *
     * @param \DOMDocument         $document
     * @param \DOMElement|\DOMNode $element
     * @param string               $key
     * @param string|string[]      $value
     *
     * @throws \DOMException
     *
     * @return void
     */
    private function addNode(DOMDocument $document, $element, string $key, $value): void
    {
        $key = \str_replace(' ', '_', $key);

        $child = $document->createElement($key);

        $element->appendChild($child);

        $this->convertElement($document, $child, $value);
    }

    /**
     * Add collection node.
     *
     * @param \DOMDocument         $document
     * @param \DOMElement|\DOMNode $element
     * @param string|string[]      $value
     *
     * @throws \DOMException
     *
     * @return void
     */
    private function addCollectionNode(DOMDocument $document, $element, $value): void
    {
        if ($element->childNodes->length === 0 && $element->attributes->length === 0) {
            $this->convertElement($document, $element, $value);
        }

        $child = $element->cloneNode();

        $element->parentNode->appendChild($child);

        $this->convertElement($document, $child, $value);
    }

    /**
     * Add sequential node.
     *
     * @param \DOMElement|\DOMNode $element
     * @param string|string[]      $value
     *
     * @return void
     */
    private function addSequentialNode($element, $value): void
    {
        if (empty($element->nodeValue)) {
            $element->nodeValue = \htmlspecialchars($value);

            return;
        }

        $child            = $element->cloneNode();
        $child->nodeValue = \htmlspecialchars($value);

        $element->parentNode->appendChild($child);
    }

    /**
     * Create the root element.
     *
     * @param \DOMDocument $document
     * @param array|string $rootElement
     *
     * @return \DOMElement
     */
    private function createRootElement(DOMDocument $document, $rootElement): DOMElement
    {
        if (\is_string($rootElement)) {
            return $document->createElement($rootElement ?: 'root');
        }

        $rootElementName = $rootElement['rootElementName'] ?? 'root';
        $element         = $document->createElement($rootElementName);

        foreach ($rootElement as $key => $value) {
            if ($key !== '_attributes' && $key !== '@attributes') {
                continue;
            }

            foreach ($rootElement[$key] as $attrKey => $attrVal) {
                $element->setAttribute($attrKey, $attrVal);
            }
        }

        return $element;
    }

    /**
     * Check if array are all sequential.
     *
     * @param array|string $value
     *
     * @return bool
     */
    private static function isArrayAllKeySequential($value): bool
    {
        if (! \is_array($value)) {
            return false;
        }

        if (\count($value) <= 0) {
            return true;
        }

        return \array_unique(\array_map('\is_int', \array_keys($value))) === [true];
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Bridge\Monolog\Formatter;

/*
 * 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.
 */

use DateTimeInterface;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
 * Formats incoming records for console output by coloring them depending on log level.
 *
 * @author Tobias Schultze <http://tobion.de>
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 * @copyright Copyright (c) 2004-2017 Fabien Potencier
 */
class ConsoleFormatter implements FormatterInterface
{
    public const SIMPLE_FORMAT = "%datetime% %start_tag%%level_name%%end_tag% <comment>[%channel%]</> %message%%context%%extra%\n";
    public const SIMPLE_DATE   = 'H:i:s';

    /**
     * Mapper for monolog level to color level.
     *
     * @var array
     */
    private static $levelColorMap = [
        Logger::DEBUG     => 'fg=white',
        Logger::INFO      => 'fg=green',
        Logger::NOTICE    => 'fg=blue',
        Logger::WARNING   => 'fg=cyan',
        Logger::ERROR     => 'fg=yellow',
        Logger::CRITICAL  => 'fg=red',
        Logger::ALERT     => 'fg=red',
        Logger::EMERGENCY => 'fg=white;bg=red',
    ];

    /**
     * Console formatter configuration.
     *
     * @var array
     */
    private $options;

    /**
     * Stream data.
     *
     * @var mixed
     */
    private $outputBuffer;

    /**
     * Configured VarCloner instance.
     *
     * @var \Symfony\Component\VarDumper\Cloner\VarCloner
     */
    private $cloner;

    /**
     * CliDumper instance.
     *
     * @var \Symfony\Component\VarDumper\Dumper\CliDumper
     */
    private $dumper;

    /**
     * Create a new console formatter instance.
     *
     * Available options:
     *   * format: The format of the outputted log string. The following placeholders are supported: %datetime%, %start_tag%, %level_name%, %end_tag%, %channel%, %message%, %context%, %extra%;
     *   * date_format: The format of the outputted date string;
     *   * colors: If true, the log string contains ANSI code to add color;
     *   * multiline: If false, "context" and "extra" are dumped on one line.
     *
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        $this->options = \array_replace([
            'format'      => self::SIMPLE_FORMAT,
            'date_format' => self::SIMPLE_DATE,
            'colors'      => true,
            'multiline'   => false,
        ], $options);

        $casterClass = $this->castObjectClass();

        if (\class_exists(VarCloner::class)) {
            $this->cloner = new VarCloner();
            $this->cloner->addCasters([
                '*' => [$casterClass, 'castObject'],
            ]);

            $this->outputBuffer = \fopen('php://memory', 'r+b');

            $output = [$this, 'echoLine'];

            if ($this->options['multiline']) {
                $output = $this->outputBuffer;
            }

            // Exits from VarDumper version >=3.3
            $commaSeparator = \defined(CliDumper::class . '::DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 4;

            $this->dumper = new CliDumper($output, null, CliDumper::DUMP_LIGHT_ARRAY | $commaSeparator);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        foreach ($records as $key => $record) {
            $records[$key] = $this->format($record);
        }

        return $records;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record     = $this->replacePlaceHolder($record);
        $levelColor = self::$levelColorMap[$record['level']];

        if ($this->options['multiline']) {
            $context = $extra = "\n";
        } else {
            $context = $extra = ' ';
        }

        $context .= $this->dumpData($record['context']);
        $extra   .= $this->dumpData($record['extra']);

        return \strtr($this->options['format'], [
            '%datetime%'   => $record['datetime']->format($this->options['date_format']),
            '%start_tag%'  => \sprintf('<%s>', $levelColor),
            '%level_name%' => \sprintf('%-9s', $record['level_name']),
            '%end_tag%'    => '</>',
            '%channel%'    => $record['channel'],
            '%message%'    => $this->replacePlaceHolder($record)['message'],
            '%context%'    => $context,
            '%extra%'      => $extra,
        ]);
    }

    /**
     * @internal
     *
     * @param mixed $line
     * @param mixed $depth
     * @param mixed $indentPad
     *
     * @return void
     */
    public function echoLine($line, $depth, $indentPad): void
    {
        if (-1 !== $depth) {
            \fwrite($this->outputBuffer, $line);
        }
    }

    /**
     * Return a anonymous class with a castObject function.
     *
     * @return object
     *
     * @codeCoverageIgnore
     */
    private function castObjectClass(): object
    {
        return new class($this->options) {
            /**
             * Console formatter configuration.
             *
             * @var array
             */
            private $options;

            public function __construct(array $options)
            {
                $this->options = $options;
            }

            /**
             * @param mixed                                    $value
             * @param array                                    $array
             * @param \Symfony\Component\VarDumper\Cloner\Stub $stub
             * @param mixed                                    $isNested
             *
             * @return array
             */
            public function castObject($value, array $array, Stub $stub, $isNested): array
            {
                if ($this->options['multiline']) {
                    return $array;
                }

                if ($isNested && ! $value instanceof DateTimeInterface) {
                    $stub->cut = -1;
                    $array     = [];
                }

                return $array;
            }
        };
    }

    /**
     * Replace message and context place holder.
     *
     * @param array $record
     *
     * @return array
     */
    private function replacePlaceHolder(array $record): array
    {
        $message = $record['message'];

        if (\mb_strpos($message, '{') === false) {
            return $record;
        }

        $context = $record['context'];

        $replacements = [];

        foreach ((array) $context as $k => $v) {
            // Remove quotes added by the dumper around string.
            $v                            = \trim($this->dumpData($v, false), '"');
            $v                            = OutputFormatter::escape($v);
            $replacements['{' . $k . '}'] = \sprintf('<comment>%s</>', $v);
        }

        $record['message'] = \strtr($message, $replacements);

        return $record;
    }

    /**
     * Dump console data.
     *
     * @param mixed     $data
     * @param null|bool $colors
     *
     * @return string
     */
    private function dumpData($data, ?bool $colors = null): string
    {
        if ($this->dumper === null) {
            return '';
        }

        if ($colors === null) {
            $this->dumper->setColors($this->options['colors']);
        } else {
            $this->dumper->setColors($colors);
        }

        if (! $data instanceof Data) {
            $data = $this->cloner->cloneVar($data);
        }

        $data = $data->withRefHandles(false);
        $this->dumper->dump($data);

        $dump = \stream_get_contents($this->outputBuffer, -1, 0);

        \rewind($this->outputBuffer);
        \ftruncate($this->outputBuffer, 0);

        return \rtrim($dump);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Profiler\DataCollector\Bridge\Cache;

use Cache\TagInterop\TaggableCacheItemPoolInterface;
use Psr\SimpleCache\CacheInterface;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\Traits\SimpleTraceableCacheDecoratorTrait;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\Traits\TraceableCacheItemDecoratorTrait;

final class PhpCacheTraceableCacheDecorator implements CacheInterface, TaggableCacheItemPoolInterface, CacheDecorator
{
    use SimpleTraceableCacheDecoratorTrait;
    use TraceableCacheItemDecoratorTrait;

    /**
     * A instance of psr16 cache.
     *
     * @var \Cache\TagInterop\TaggableCacheItemPoolInterface|\Psr\SimpleCache\CacheInterface
     */
    private $pool;

    /**
     * List of event calls.
     *
     * @var array
     */
    private $calls = [];

    /**
     * Original class name.
     *
     * @var string
     */
    private $name;

    /**
     * Create new Php Cache Traceable Cache Decorator instance.
     *
     * @param \Cache\Adapter\Common\PhpCachePool|\Psr\SimpleCache\CacheInterface $pool
     */
    public function __construct($pool)
    {
        $this->pool = $pool;
        $this->name = \get_class($pool);
    }

    /**
     * {@inheritdoc}
     */
    public function getCalls(): array
    {
        try {
            return $this->calls;
        } finally {
            $this->calls = [];
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    public function clear(): ?bool
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result = $this->pool->clear();
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function invalidateTags(array $tags): bool
    {
        $event = $this->start(__FUNCTION__);

        try {
            $bool = $event->result = $this->pool->invalidateTags($tags);
        } finally {
            $event->end = \microtime(true);
        }

        return $bool;
    }

    /**
     * {@inheritdoc}
     */
    public function invalidateTag($tag)
    {
        $event = $this->start(__FUNCTION__);

        try {
            $bool = $event->result = $this->pool->invalidateTag($tag);
        } finally {
            $event->end = \microtime(true);
        }

        return $bool;
    }

    /**
     * Start new event.
     *
     * @param string $name
     *
     * @return \Viserio\Component\Profiler\DataCollector\Bridge\Cache\TraceableCollector
     */
    private function start(string $name): object
    {
        $this->calls[] = $event = new TraceableCollector();

        $event->name  = $name;
        $event->start = \microtime(true);

        return $event;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Events\DataCollector;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use SplObjectStorage;
use Symfony\Component\Stopwatch\Stopwatch;
use Throwable;
use Viserio\Component\Contract\Events\Event as EventContract;
use Viserio\Component\Contract\Events\EventManager as EventManagerContract;
use Viserio\Component\Contract\Events\Traits\EventManagerAwareTrait;
use Viserio\Component\Events\Event;
use Viserio\Component\Events\EventManager;

/**
 * Some of this code has been ported from Symfony. The original
 * code is (c) Fabien Potencier <fabien@symfony.com>.
 *
 * @method array getListeners(string $eventName = null)
 */
class TraceableEventManager implements EventManagerContract, LoggerAwareInterface
{
    use LoggerAwareTrait;
    use EventManagerAwareTrait;

    /**
     * List of called listeners.
     *
     * @var array
     */
    private $called = [];

    /**
     * List of wrapped listeners.
     *
     * @var array
     */
    private $wrappedListeners = [];

    /**
     * List of orphaned listeners.
     *
     * @var array
     */
    private $orphanedEvents = [];

    /**
     * @var null|\Symfony\Component\Stopwatch\Stopwatch
     */
    private $stopwatch;

    /**
     * Create a new TraceableEventManager instance.
     *
     * @param null|\Viserio\Component\Events\EventManager $eventManager
     * @param \Symfony\Component\Stopwatch\Stopwatch      $stopwatch
     *
     * @throws \Viserio\Component\Contract\Events\Exception\RuntimeException
     */
    public function __construct(EventManager $eventManager, Stopwatch $stopwatch)
    {
        $this->eventManager = $eventManager;
        $this->stopwatch    = $stopwatch;
        $this->logger       = new NullLogger();
    }

    /**
     * Proxy all method calls to the original event manager.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @return mixed
     */
    public function __call(string $method, array $parameters)
    {
        return $this->eventManager->{$method}(...$parameters);
    }

    /**
     * Gets the orphaned events.
     *
     * @return array An array of orphaned events
     */
    public function getOrphanedEvents(): array
    {
        return $this->orphanedEvents;
    }

    /**
     * {@inheritdoc}
     */
    public function attach(string $eventName, $listener, int $priority = 0): void
    {
        $this->eventManager->attach($eventName, $listener, $priority);
    }

    /**
     * {@inheritdoc}
     */
    public function trigger($event, $target = null, array $argv = []): bool
    {
        if ((! \is_object($event) && ! ($event instanceof EventContract)) && \is_string($event)) {
            $event = new Event($event, $target, $argv);
        }

        if ($event->isPropagationStopped()) {
            $this->logger->debug(\sprintf('The [%s] event is already stopped. No listeners have been called.', $event->getName()));

            return false;
        }

        $this->preProcess($event->getName());

        $stopWatch = $this->stopwatch->start($event->getName(), 'section');

        $return = $this->eventManager->trigger($event, $target, $argv);

        if ($stopWatch->isStarted()) {
            $stopWatch->stop();
        }

        $this->postProcess($event->getName());

        return $return;
    }

    /**
     * {@inheritdoc}
     */
    public function detach(string $eventName, $listener): bool
    {
        if (isset($this->wrappedListeners[$eventName])) {
            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
                /** @var WrappedListener $wrappedListener */
                if ($wrappedListener->getWrappedListener() === $listener) {
                    $listener = $wrappedListener;
                    unset($this->wrappedListeners[$eventName][$index]);

                    break;
                }
            }
        }

        return $this->eventManager->detach($eventName, $listener);
    }

    /**
     * {@inheritdoc}
     */
    public function clearListeners(string $eventName): void
    {
        $this->eventManager->clearListeners($eventName);
    }

    /**
     * Gets the called listeners.
     *
     * @return array An array of called listeners
     */
    public function getCalledListeners(): array
    {
        $called = [];

        foreach ($this->called as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $called[$eventName][] = $listener->getInfo($eventName);
            }
        }

        return $called;
    }

    /**
     * Gets the not called listeners.
     *
     * @return array An array of not called listeners
     */
    public function getNotCalledListeners(): array
    {
        try {
            $allListeners = $this->eventManager->getListeners();
        } catch (Throwable $e) {
            $this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);

            // unable to retrieve the uncalled listeners
            return [];
        }

        $notCalled = [];

        foreach ($allListeners as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $called = false;

                if (isset($this->called[$eventName])) {
                    foreach ($this->called[$eventName] as $calledListener) {
                        /** @var WrappedListener $calledListener */
                        if ($calledListener->getWrappedListener() === $listener) {
                            $called = true;

                            break;
                        }
                    }
                }

                if (! $called) {
                    if (! $listener instanceof WrappedListener) {
                        $listener = new WrappedListener($listener, null, $this->stopwatch, $this);
                    }

                    $notCalled[$eventName][] = $listener->getInfo($eventName);
                }
            }
        }

        \uasort($notCalled, [$this, 'sortListenersByPriority']);

        return $notCalled;
    }

    /**
     * Resets this to its initial state.
     *
     * @return void
     */
    public function reset(): void
    {
        $this->called = $this->orphanedEvents = [];
    }

    /**
     * Gets the listener priority for a specific event.
     *
     * Returns null if the event or the listener does not exist.
     *
     * @param string         $eventName The name of the event
     * @param array|callable $listener  The listener
     *
     * @return null|int The event listener priority
     *
     * @internal
     */
    public function getListenerPriority(string $eventName, $listener): ?int
    {
        // we might have wrapped listeners for the event (if called while dispatching)
        // in that case get the priority by wrapper
        if (isset($this->wrappedListeners[$eventName])) {
            /** @var \Viserio\Component\Events\DataCollector\WrappedListener $wrappedListener */
            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
                if ($wrappedListener->getWrappedListener() === $listener) {
                    return $this->eventManager->getListenerPriority($eventName, $wrappedListener);
                }
            }
        }

        return $this->eventManager->getListenerPriority($eventName, $listener);
    }

    /**
     * @param string $eventName
     *
     * @return void
     */
    private function preProcess(string $eventName): void
    {
        if (\count($this->eventManager->getListeners($eventName)) === 0) {
            $this->orphanedEvents[] = $eventName;
        }

        foreach ($this->eventManager->getListeners($eventName) as $listener) {
            $priority        = $this->getListenerPriority($eventName, $listener);
            $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this);

            $this->wrappedListeners[$eventName][] = $wrappedListener;

            $this->eventManager->detach($eventName, $listener);
            $this->eventManager->attach($eventName, $wrappedListener, $priority);
        }
    }

    /**
     * @param string $eventName
     */
    private function postProcess(string $eventName): void
    {
        unset($this->wrappedListeners[$eventName]);

        $skipped = false;

        foreach ($this->eventManager->getListeners($eventName) as $listener) {
            if (! $listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
                continue;
            }

            // Unwrap listener
            $priority = $this->getListenerPriority($eventName, $listener);

            $this->eventManager->detach($eventName, $listener);
            $this->eventManager->attach($eventName, $listener->getWrappedListener(), $priority);

            $context = ['event' => $eventName, 'listener' => $listener->getPretty()];

            if ($listener->wasCalled()) {
                $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);

                if (! isset($this->called[$eventName])) {
                    $this->called[$eventName] = new SplObjectStorage();
                }

                $this->called[$eventName]->attach($listener);
            }

            if ($skipped) {
                $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
            }

            if ($listener->isPropagationStopped()) {
                $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);

                $skipped = true;
            }
        }
    }

    /**
     * @param array $a
     * @param array $b
     *
     * @return int
     */
    private function sortListenersByPriority(array $a, array $b): int
    {
        if (\is_int($a['priority']) && ! \is_int($b['priority'])) {
            return 1;
        }

        if (! \is_int($a['priority']) && \is_int($b['priority'])) {
            return -1;
        }

        if ($a['priority'] === $b['priority']) {
            return 0;
        }

        if ($a['priority'] > $b['priority']) {
            return -1;
        }

        return 1;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Cookie;

use Cake\Chronos\Chronos;
use DateTime;
use DateTimeInterface;
use Viserio\Component\Contract\Cookie\Cookie as CookieContract;
use Viserio\Component\Contract\Cookie\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Support\Stringable as StringableContract;

abstract class AbstractCookie implements StringableContract, CookieContract
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var null|string
     */
    protected $value;

    /**
     * @var null|string
     */
    protected $domain;

    /**
     * @var null|int
     */
    protected $expires;

    /**
     * @var null|int
     */
    protected $maxAge;

    /**
     * @var string
     */
    protected $path;

    /**
     * @var bool
     */
    protected $secure;

    /**
     * @var bool
     */
    protected $httpOnly;

    /**
     * @var null|bool|string
     */
    protected $sameSite;

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    public function getValue(): ?string
    {
        return $this->value;
    }

    /**
     * {@inheritdoc}
     */
    public function hasValue(): bool
    {
        return ! empty($this->value);
    }

    /**
     * {@inheritdoc}
     */
    public function getDomain(): ?string
    {
        return $this->domain;
    }

    /**
     * {@inheritdoc}
     */
    public function hasDomain(): bool
    {
        return $this->domain !== null;
    }

    /**
     * {@inheritdoc}
     */
    public function hasExpires(): bool
    {
        return $this->expires !== 0;
    }

    /**
     * {@inheritdoc}
     */
    public function getMaxAge(): ?int
    {
        return $this->maxAge;
    }

    /**
     * {@inheritdoc}
     */
    public function hasMaxAge(): bool
    {
        return $this->maxAge !== null;
    }

    /**
     * {@inheritdoc}
     */
    public function getPath(): string
    {
        return $this->path;
    }

    /**
     * {@inheritdoc}
     */
    public function isSecure(): bool
    {
        return $this->secure;
    }

    /**
     * {@inheritdoc}
     */
    public function isHttpOnly(): bool
    {
        return $this->httpOnly;
    }

    /**
     * {@inheritdoc}
     */
    public function getSameSite()
    {
        return $this->sameSite;
    }

    /**
     * {@inheritdoc}
     */
    public function isSameSite(): bool
    {
        return $this->sameSite !== false;
    }

    /**
     * {@inheritdoc}
     */
    abstract public function withValue(string $value = null): CookieContract;

    /**
     * {@inheritdoc}
     */
    public function withMaxAge(int $maxAge = null): CookieContract
    {
        $new         = clone $this;
        $new->maxAge = $maxAge;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withExpires($expires): CookieContract
    {
        $new          = clone $this;
        $new->expires = $this->normalizeExpires($expires);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getExpiresTime(): ?int
    {
        return $this->expires;
    }

    /**
     * {@inheritdoc}
     */
    public function isExpired(): bool
    {
        return $this->expires !== 0 &&
            Chronos::parse(\gmdate('D, d-M-Y H:i:s', $this->expires)) < Chronos::now();
    }

    /**
     * {@inheritdoc}
     */
    public function withDomain(string $domain = null): CookieContract
    {
        $new         = clone $this;
        $new->domain = $this->normalizeDomain($domain);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withPath(string $path = '/'): CookieContract
    {
        $new       = clone $this;
        $new->path = $this->normalizePath($path);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withSecure(bool $secure): CookieContract
    {
        $new         = clone $this;
        $new->secure = $secure;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withHttpOnly(bool $httpOnly): CookieContract
    {
        $new           = clone $this;
        $new->httpOnly = $httpOnly;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withSameSite($sameSite): CookieContract
    {
        $new           = clone $this;
        $new->sameSite = $this->validateSameSite($sameSite);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function matchPath(string $path): bool
    {
        return $this->path === $path || (\mb_strpos($path, $this->path . '/') === 0);
    }

    /**
     * {@inheritdoc}
     */
    public function matchCookie(CookieContract $cookie): bool
    {
        return $this->getName() === $cookie->getName()   &&
            $this->getDomain()  === $cookie->getDomain() &&
            $this->getPath()    === $cookie->getPath();
    }

    /**
     * {@inheritdoc}
     */
    public function matchDomain(string $domain): bool
    {
        // Domain is not set or exact match
        if (! $this->hasDomain() || \strcasecmp($domain, $this->getDomain()) === 0) {
            return true;
        }

        // Domain is not an IP address
        if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
            return false;
        }

        return (bool) \preg_match('/\b' . \preg_quote($this->getDomain(), '/') . '$/i', $domain);
    }

    /**
     * Validate SameSite value.
     *
     * @param bool|string $sameSite
     *
     * @return bool|string
     */
    protected function validateSameSite($sameSite)
    {
        if (! \in_array($sameSite, [self::SAMESITE_STRICT, self::SAMESITE_LAX], true)) {
            return false;
        }

        return $sameSite;
    }

    /**
     * Normalizes the expiration value.
     *
     * @param null|\DateTimeInterface|int|string $expiration
     *
     * @throws \Viserio\Component\Contract\Cookie\Exception\InvalidArgumentException
     *
     * @return int
     */
    protected function normalizeExpires($expiration = null): int
    {
        $expires   = $this->getTimestamp($expiration);
        $tsExpires = $expires;

        if (\is_string($expires)) {
            $tsExpires = \strtotime($expires);
            $is32Bit   = \PHP_INT_SIZE <= 4;

            // if $tsExpires is invalid and PHP is compiled as 32bit. Check if it fail reason is the 2038 bug
            if ($is32Bit && ! \is_int($tsExpires)) {
                $dateTime = new Chronos($expires);

                if ($dateTime->format('Y') > 2038) {
                    $tsExpires = \PHP_INT_MAX;
                }
            }
        }

        if (! \is_int($tsExpires) || $tsExpires < 0) {
            throw new InvalidArgumentException('Invalid expires time specified.');
        }

        return $tsExpires;
    }

    /**
     * Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
     *
     * @param null|string $domain
     *
     * @return null|string
     *
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
     */
    protected function normalizeDomain(string $domain = null): ?string
    {
        if ($domain !== null) {
            $domain = \mb_strtolower(\ltrim($domain, '.'));
        }

        return $domain;
    }

    /**
     * Processes path as per spec in RFC 6265.
     *
     * @param string $path
     *
     * @return string
     *
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
     */
    protected function normalizePath(string $path): string
    {
        $path = \rtrim($path, '/');

        if (empty($path) || \mb_strpos($path, '/')) {
            $path = '/';
        }

        return $path;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedNameAndValuePartIfSet(array $cookieStringParts): array
    {
        $name = \urlencode($this->name) . '=';

        if ($this->getValue() === null) {
            $time = Chronos::now()->getTimestamp() - 31536001;

            $cookieStringParts[] .= $name . 'deleted; Expires=' . (new Chronos(\gmdate('D, d-M-Y H:i:s', $time)))->toCookieString();
        } else {
            $cookieStringParts[] .= $name . \urlencode($this->getValue());

            if (null !== $this->getExpiresTime()) {
                $cookieStringParts[] .= 'Expires=' . (new Chronos(\gmdate('D, d-M-Y H:i:s', $this->getExpiresTime())))->toCookieString();
            }
        }

        return $cookieStringParts;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedDomainPartIfSet(array $cookieStringParts): array
    {
        if ($this->domain !== null) {
            $cookieStringParts[] = \sprintf('Domain=%s', $this->domain);
        }

        return $cookieStringParts;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedPathPartIfSet(array $cookieStringParts): array
    {
        if ($this->path !== null) {
            $cookieStringParts[] = \sprintf('Path=%s', $this->path);
        }

        return $cookieStringParts;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedMaxAgePartIfSet(array $cookieStringParts): array
    {
        if ($this->maxAge) {
            $cookieStringParts[] = \sprintf('Max-Age=%s', $this->maxAge);
        }

        return $cookieStringParts;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedSecurePartIfSet(array $cookieStringParts): array
    {
        if ($this->secure) {
            $cookieStringParts[] = 'Secure';
        }

        return $cookieStringParts;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedHttpOnlyPartIfSet(array $cookieStringParts): array
    {
        if ($this->httpOnly) {
            $cookieStringParts[] = 'HttpOnly';
        }

        return $cookieStringParts;
    }

    /**
     * @param array $cookieStringParts
     *
     * @return array
     */
    protected function appendFormattedSameSitePartIfSet(array $cookieStringParts): array
    {
        if ($this->sameSite) {
            $cookieStringParts[] = \sprintf('SameSite=%s', $this->sameSite);
        }

        return $cookieStringParts;
    }

    /**
     * Check if the string / int is a unix timestamp.
     *
     * @param int|string $timestamp
     *
     * @return bool
     */
    protected function isValidTimeStamp($timestamp): bool
    {
        return ((int) $timestamp <= \PHP_INT_MAX) && ((int) $timestamp >= ~\PHP_INT_MAX);
    }

    /**
     * Get timestamp as cookie string format.
     *
     * @param null|\DateTimeInterface|int|string $expiration
     *
     * @return null|string
     */
    protected function getTimestamp($expiration): ?string
    {
        if (\is_int($expiration) && \mb_strlen((string) $expiration) === 10 && $this->isValidTimeStamp($expiration)) {
            return Chronos::createFromTimestamp($expiration)->toCookieString();
        }

        if (\is_int($expiration)) {
            return Chronos::now()->addSeconds($expiration)->toCookieString();
        }

        if ($expiration instanceof DateTimeInterface) {
            return $expiration->format(DateTime::COOKIE);
        }

        if (\is_string($expiration)) {
            return $expiration;
        }

        return null;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Profiler\DataCollector\Bridge\Cache;

use Psr\Cache\CacheItemPoolInterface;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\Traits\TraceableCacheItemDecoratorTrait;

/**
 * Ported from symfony, see original.
 *
 * @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 */
final class TraceableCacheItemDecorator implements CacheItemPoolInterface, CacheDecorator
{
    use TraceableCacheItemDecoratorTrait;

    /**
     * List of event calls.
     *
     * @var array
     */
    private $calls = [];

    /**
     * A instance of the item pool.
     *
     * @var \Psr\Cache\CacheItemPoolInterface
     */
    private $pool;

    /**
     * Original class name.
     *
     * @var string
     */
    private $name;

    /**
     * Create new Traceable Cache Item Decorator instance.
     *
     * @param \Psr\Cache\CacheItemPoolInterface $pool
     */
    public function __construct(CacheItemPoolInterface $pool)
    {
        $this->name = \get_class($pool);
        $this->pool = $pool;
    }

    /**
     * {@inheritdoc}
     */
    public function getCalls(): array
    {
        try {
            return $this->calls;
        } finally {
            $this->calls = [];
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * {@inheritdoc}
     */
    public function clear(): ?bool
    {
        $event = $this->start(__FUNCTION__);

        try {
            return $event->result = $this->pool->clear();
        } finally {
            $event->end = \microtime(true);
        }
    }

    /**
     * Start new event.
     *
     * @param string $name
     *
     * @return \Viserio\Component\Profiler\DataCollector\Bridge\Cache\TraceableCollector
     */
    private function start(string $name): TraceableCollector
    {
        $this->calls[] = $event = new TraceableCollector();

        $event->name  = $name;
        $event->start = \microtime(true);

        return $event;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Translation;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\NullLogger;
use Viserio\Component\Contract\Translation\MessageCatalogue as MessageCatalogueContract;
use Viserio\Component\Contract\Translation\MessageFormatter as MessageFormatterContract;
use Viserio\Component\Contract\Translation\Translator as TranslatorContract;
use Viserio\Component\Translation\Traits\ValidateLocaleTrait;

class Translator implements TranslatorContract, LoggerAwareInterface
{
    use LoggerAwareTrait;
    use ValidateLocaleTrait;

    /**
     * Formatter instance.
     *
     * @var \Viserio\Component\Contract\Translation\MessageFormatter
     */
    protected $formatter;

    /**
     * The message catalogue.
     *
     * @var \Viserio\Component\Contract\Translation\MessageCatalogue
     */
    protected $catalogue;

    /**
     * All registered filters.
     *
     * @var array
     */
    protected $filters = [];

    /**
     * All registered helpers.
     *
     * @var array
     */
    protected $helpers = [];

    /**
     * Default language to translate into. (e.g. 'en').
     *
     * @var string
     */
    protected $locale;

    /**
     * All collected massages.
     *
     * @var array
     */
    protected $messages = [];

    /**
     * Create new Translator instance.
     *
     * @param \Viserio\Component\Contract\Translation\MessageCatalogue $catalogue
     * @param \Viserio\Component\Contract\Translation\MessageFormatter $formatter
     *
     * @throws \InvalidArgumentException If a locale contains invalid characters
     */
    public function __construct(MessageCatalogueContract $catalogue, MessageFormatterContract $formatter)
    {
        $this->setLocale($catalogue->getLocale());

        $this->catalogue = $catalogue;
        $this->formatter = $formatter;
        $this->logger    = new NullLogger();
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter(): MessageFormatterContract
    {
        return $this->formatter;
    }

    /**
     * {@inheritdoc}
     */
    public function getCatalogue(): MessageCatalogueContract
    {
        return $this->catalogue;
    }

    /**
     * {@inheritdoc}
     */
    public function getLocale(): string
    {
        return $this->locale;
    }

    /**
     * {@inheritdoc}
     */
    public function setLocale(string $locale): TranslatorContract
    {
        self::assertValidLocale($locale);
        $this->locale = $locale;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function trans(string $id, array $parameters = [], string $domain = 'messages'): string
    {
        if (\preg_match('/^(.*?)(\\[.*?\\])$/', $id, $match) === 1) {
            $id = $match[1];
        }

        $trans = $this->formatter->format(
            $this->catalogue->get($id, $domain),
            $this->locale,
            $parameters
        );
        // Add filter and helper back
        if (isset($match[2])) {
            $trans = $trans . $match[2];
        }

        $trans = $this->applyFilters($trans);
        $trans = $this->applyHelpers($trans);

        $this->log($id, $domain);
        $this->collectMessage($this->locale, $domain, $id, $trans, $parameters);

        return $trans;
    }

    /**
     * {@inheritdoc}
     */
    public function addHelper(string $name, callable $helper): TranslatorContract
    {
        $this->helpers[$name] = $helper;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function addFilter(callable $filter): TranslatorContract
    {
        $this->filters[] = $filter;

        return $this;
    }

    /**
     * Get all collected messages.
     *
     * @return array
     */
    public function getCollectedMessages(): array
    {
        return $this->messages;
    }

    /**
     * Apply helpers.
     *
     * @param string $translation
     *
     * @return mixed
     */
    protected function applyHelpers(string $translation)
    {
        $helpers = $this->filterHelpersFromString($translation);

        if (\count($this->helpers) === 0 || \count($helpers) === 0) {
            return $translation;
        }

        foreach ($helpers as $helper) {
            if (! isset($this->helpers[$helper['name']])) {
                return $translation;
            }

            \array_unshift($helper['arguments'], $translation);

            $translation = \call_user_func_array($this->helpers[$helper['name']], $helper['arguments']);
        }

        return $translation;
    }

    /**
     * Filter a helper from string.
     *
     * @param string $translation
     *
     * @return array
     */
    protected function filterHelpersFromString(string $translation): array
    {
        $helpers = [];

        if (\preg_match('/^(.*?)\\[(.*?)\\]$/', $translation, $match) === 1) {
            $helpers = \explode('|', $match[2]);
            $helpers = \array_map(static function ($helper) {
                $name = $helper;
                $arguments = [];

                if (\preg_match('/^(.*?)\:(.*)$/', $helper, $match) === 1) {
                    $name = $match[1];
                    $arguments = \explode(':', $match[2]);
                }

                return [
                    'name'      => $name,
                    'arguments' => $arguments,
                ];
            }, $helpers);
        }

        return $helpers;
    }

    /**
     * Apply filter on string.
     *
     * @param string $translation
     *
     * @return string
     */
    protected function applyFilters(string $translation): string
    {
        if (\count($this->filters) === 0) {
            return $translation;
        }

        foreach ($this->filters as $filter) {
            $translation = $filter($translation);
        }

        return $translation;
    }

    /**
     * Logs for missing translations.
     *
     * @param string $id
     * @param string $domain
     *
     * @return void
     */
    protected function log(string $id, string $domain): void
    {
        $catalogue = $this->catalogue;

        if ($catalogue->defines($id, $domain)) {
            return;
        }

        if ($catalogue->has($id, $domain)) {
            $this->logger->debug(
                'Translation use fallback catalogue.',
                ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]
            );
        } else {
            $this->logger->warning(
                'Translation not found.',
                ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]
            );
        }
    }

    /**
     * Collect messages about all translations.
     *
     * @param null|string $locale
     * @param string      $domain
     * @param string      $id
     * @param string      $translation
     * @param array       $parameters
     *
     * @return void
     */
    protected function collectMessage(
        ?string $locale,
        string $domain,
        string $id,
        string $translation,
        array $parameters = []
    ): void {
        $catalogue = $this->catalogue;

        if ($catalogue->defines($id, $domain)) {
            $state = self::MESSAGE_DEFINED;
        } elseif ($catalogue->has($id, $domain)) {
            $state             = self::MESSAGE_EQUALS_FALLBACK;
            $fallbackCatalogue = $catalogue->getFallbackCatalogue();

            while ($fallbackCatalogue) {
                if ($fallbackCatalogue->defines($id, $domain)) {
                    $locale = $fallbackCatalogue->getLocale();

                    break;
                }

                $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
            }
        } else {
            $state = self::MESSAGE_MISSING;
        }

        $this->messages[] = [
            'locale'      => $locale,
            'domain'      => $domain,
            'id'          => $id,
            'translation' => $translation,
            'parameters'  => $parameters,
            'state'       => $state,
        ];
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Bridge\Twig\Extension;

use Twig\Extension\AbstractExtension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Viserio\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor;
use Viserio\Bridge\Twig\NodeVisitor\TranslationNodeVisitor;
use Viserio\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser;
use Viserio\Bridge\Twig\TokenParser\TransTokenParser;
use Viserio\Component\Contract\Translation\TranslationManager as TranslationManagerContract;
use Viserio\Component\Contract\Translation\Translator as TranslatorContract;

class TranslatorExtension extends AbstractExtension
{
    /**
     * Translation instance.
     *
     * @var null|\Viserio\Component\Contract\Translation\TranslationManager
     */
    protected $translationManager;

    /**
     * A instance of NodeVisitorInterface.
     *
     * @var \Twig\NodeVisitor\NodeVisitorInterface|\Viserio\Bridge\Twig\NodeVisitor\TranslationNodeVisitor
     */
    private $translationNodeVisitor;

    /**
     * Create a new translator extension.
     *
     * @param \Viserio\Component\Contract\Translation\TranslationManager                                          $translationManager
     * @param null|\Twig\NodeVisitor\NodeVisitorInterface|\Viserio\Bridge\Twig\NodeVisitor\TranslationNodeVisitor $translationNodeVisitor
     */
    public function __construct(
        TranslationManagerContract $translationManager,
        ?NodeVisitorInterface $translationNodeVisitor = null
    ) {
        $this->translationManager = $translationManager;

        if ($translationNodeVisitor === null) {
            $translationNodeVisitor = new TranslationNodeVisitor();
        }

        $this->translationNodeVisitor = $translationNodeVisitor;
    }

    /**
     * Get a translation node visitor instance.
     *
     * @return \Twig\NodeVisitor\NodeVisitorInterface|\Viserio\Bridge\Twig\NodeVisitor\TranslationNodeVisitor
     */
    public function getTranslationNodeVisitor()
    {
        return $this->translationNodeVisitor;
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        return 'Viserio_Bridge_Twig_Extension_Translator';
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions(): array
    {
        return [
            new TwigFunction('trans', [$this, 'trans']),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFilters(): array
    {
        return [
            new TwigFilter(
                'trans',
                [$this, 'trans'],
                [
                    'pre_escape' => 'html',
                    'is_safe'    => ['html'],
                ]
            ),
        ];
    }

    /**
     * Returns the token parser instance to add to the existing list.
     *
     * @return \Twig\TokenParser\AbstractTokenParser[]
     */
    public function getTokenParsers(): array
    {
        return [
            // {% trans %}Narrowspark is great!{% endtrans %}
            // or
            // {% trans %}
            //     {count,plural,=0{No candy left}one{Got # candy left}other{Got # candies left}}
            // {% endtrans %}
            new TransTokenParser(),
            // {% trans_default_domain "foobar" %}
            new TransDefaultDomainTokenParser(),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getNodeVisitors(): array
    {
        return [$this->translationNodeVisitor, new TranslationDefaultDomainNodeVisitor()];
    }

    /**
     * Get a language translator instance.
     *
     * @param null|string $locale
     *
     * @throws \RuntimeException
     *
     * @return \Viserio\Component\Contract\Translation\Translator
     */
    public function getTranslator(?string $locale = null): TranslatorContract
    {
        return $this->translationManager->getTranslator($locale);
    }

    /**
     * Translates the given message.
     *
     * @param string      $id         The message id
     * @param mixed       $parameters An array of parameters for the message
     * @param string      $domain     The domain for the message or null to use the default
     * @param null|string $locale     The locale to change the translator language
     *
     * @throws \InvalidArgumentException If the locale contains invalid characters
     * @throws \RuntimeException         If no translator found
     *
     * @return string The translated string
     */
    public function trans(string $id, $parameters = [], string $domain = 'messages', ?string $locale = null): string
    {
        if (\is_numeric($parameters)) {
            $parameters = ['count' => $parameters];
        }

        return $this->translationManager->getTranslator($locale)->trans($id, $parameters, $domain);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Session\Handler;

use SessionHandlerInterface;
use SessionUpdateTimestampHandlerInterface;
use Viserio\Component\Contract\Session\Exception\LogicException;

/**
 * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class StrictSessionHandler extends AbstractSessionHandler
{
    /**
     * @var \SessionHandlerInterface
     */
    private $handler;

    /**
     * @var bool
     */
    private $doDestroy;

    /**
     * StrictSessionHandler constructor.
     *
     * @param \SessionHandlerInterface $handler
     *
     * @throws \Viserio\Component\Contract\Session\Exception\LogicException
     */
    public function __construct(SessionHandlerInterface $handler)
    {
        if ($handler instanceof SessionUpdateTimestampHandlerInterface) {
            throw new LogicException(
                \sprintf(
                '[%s] is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with [%s].',
                \get_class($handler),
                self::class
            )
            );
        }

        $this->handler = $handler;
    }

    /**
     * {@inheritdoc}
     */
    public function open($savePath, $sessionName): bool
    {
        parent::open($savePath, $sessionName);

        return $this->handler->open($savePath, $sessionName);
    }

    /**
     * {@inheritdoc}
     */
    public function updateTimestamp($sessionId, $data): bool
    {
        return $this->write($sessionId, $data);
    }

    /**
     * {@inheritdoc}
     */
    public function destroy($sessionId): bool
    {
        $this->doDestroy = true;

        $destroyed = parent::destroy($sessionId);

        return $this->doDestroy === true ? $this->doDestroy($sessionId) : $destroyed;
    }

    /**
     * {@inheritdoc}
     */
    public function close(): bool
    {
        return $this->handler->close();
    }

    /**
     * {@inheritdoc}
     */
    public function gc($maxlifetime): bool
    {
        return $this->handler->gc($maxlifetime);
    }

    /**
     * {@inheritdoc}
     */
    protected function doDestroy($sessionId): bool
    {
        $this->doDestroy = false;

        return $this->handler->destroy($sessionId);
    }

    /**
     * {@inheritdoc}
     */
    protected function doRead($sessionId): string
    {
        return $this->handler->read($sessionId);
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($sessionId, $data): bool
    {
        return $this->handler->write($sessionId, $data);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Parser\Command;

use FilesystemIterator;
use Generator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\Parser\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Parser\Exception\RuntimeException;

abstract class AbstractLintCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     *
     * @throws \Viserio\Component\Contract\Parser\Exception\RuntimeException
     */
    public function handle(): int
    {
        $filename            = $this->argument('filename');
        $format              = $this->option('format');
        $displayCorrectFiles = $this->getOutput()->isVerbose();

        if (! $filename) {
            if (! $stdin = $this->getStdin()) {
                throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
            }

            return $this->display([$this->validate($stdin)], $format, $displayCorrectFiles);
        }

        if (! \is_readable($filename)) {
            throw new RuntimeException(\sprintf('File or directory [%s] is not readable.', $filename));
        }

        $filesInfo = [];

        foreach ($this->getFiles($filename) as $file) {
            $filesInfo[] = $this->validate(\file_get_contents($file), $file);
        }

        return $this->display($filesInfo, $format, $displayCorrectFiles);
    }

    /**
     * Get display type from format.
     *
     * @param string $format
     * @param array  $files
     * @param bool   $displayCorrectFiles
     *
     * @throws \Viserio\Component\Contract\Parser\Exception\InvalidArgumentException
     *
     * @return int
     */
    protected function display(array $files, string $format, bool $displayCorrectFiles): int
    {
        switch ($format) {
            case 'txt':
                return $this->displayTxt($files, $displayCorrectFiles);
            case 'json':
                return $this->displayJson($files);

            default:
                throw new InvalidArgumentException(\sprintf('The format [%s] is not supported.', $format));
        }
    }

    /**
     * Display errors in txt format.
     *
     * @param array $filesInfo
     * @param bool  $displayCorrectFiles
     *
     * @return int
     */
    abstract protected function displayTxt(array $filesInfo, bool $displayCorrectFiles): int;

    /**
     * Display errors in json format.
     *
     * @param array $filesInfo
     *
     * @return int
     */
    protected function displayJson(array $filesInfo): int
    {
        $errors = 0;

        \array_walk($filesInfo, static function (&$v) use (&$errors): void {
            $v['file'] = (string) $v['file'];

            if (! $v['valid']) {
                $errors++;
            }
        });

        $this->getOutput()->writeln(\json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));

        return \min($errors, 1);
    }

    /**
     * Validate file content.
     *
     * @param string      $content
     * @param null|string $file
     *
     * @return array
     */
    abstract protected function validate(string $content, ?string $file = null): array;

    /**
     * Get a generator of files.
     *
     * @param string $fileOrDirectory
     *
     * @return \Generator
     */
    protected function getFiles(string $fileOrDirectory): Generator
    {
        if (\is_file($fileOrDirectory)) {
            yield $fileOrDirectory;

            return;
        }

        foreach (self::getDirectoryIterator($fileOrDirectory) as $file) {
            if (! \in_array($file->getExtension(), ['xlf', 'xliff'], true)) {
                continue;
            }

            yield (string) $file;
        }
    }

    /**
     * Get content from stdin.
     *
     * @return null|string
     */
    protected function getStdin(): ?string
    {
        if (\ftell(\STDIN) !== 0) {
            return null;
        }

        $inputs = '';

        while (! \feof(\STDIN)) {
            $inputs .= \fread(\STDIN, 1024);
        }

        return $inputs;
    }

    /**
     * Get item from dirs.
     *
     * @param string $directory
     *
     * @return \RecursiveIteratorIterator
     */
    protected static function getDirectoryIterator(string $directory): RecursiveIteratorIterator
    {
        return new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
            RecursiveIteratorIterator::LEAVES_ONLY
        );
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\WebServer\Provider;

use Interop\Container\ServiceProviderInterface;
use Monolog\Formatter\FormatterInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\VarDumper\Server\DumpServer;
use Symfony\Component\VarDumper\VarDumper;
use Viserio\Bridge\Monolog\Formatter\ConsoleFormatter;
use Viserio\Component\Console\Application;
use Viserio\Component\Contract\Console\Kernel as ConsoleKernelContract;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;
use Viserio\Component\WebServer\Command\ServerDumpCommand;
use Viserio\Component\WebServer\Command\ServerLogCommand;
use Viserio\Component\WebServer\Command\ServerServeCommand;
use Viserio\Component\WebServer\Command\ServerStartCommand;
use Viserio\Component\WebServer\Command\ServerStatusCommand;
use Viserio\Component\WebServer\Command\ServerStopCommand;

class ConsoleCommandsServiceProvider implements
    ServiceProviderInterface,
    RequiresComponentConfigContract,
    ProvidesDefaultOptionsContract
{
    use OptionsResolverTrait;

    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [
            Application::class => [self::class, 'extendConsole'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDimensions(): array
    {
        return ['viserio'];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        $commands = [
            'server:serve'    => ServerServeCommand::class,
            'server:start'    => ServerStartCommand::class,
            'server:stop'     => ServerStopCommand::class,
            'server:status'   => ServerStatusCommand::class,
        ];

        if (\class_exists(VarDumper::class)) {
            $commands['server:dump'] = ServerDumpCommand::class;
        }

        if (\class_exists(ConsoleFormatter::class) && \interface_exists(FormatterInterface::class)) {
            $commands['server:log'] = ServerLogCommand::class;
        }

        return [
            'console' => [
                'lazily_commands' => $commands,
            ],
            'webserver' => [
                'web_folder' => 'public',
            ],
        ];
    }

    /**
     * Extend viserio console with commands.
     *
     * @param \Psr\Container\ContainerInterface           $container
     * @param null|\Viserio\Component\Console\Application $console
     *
     * @return null|\Viserio\Component\Console\Application
     */
    public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application
    {
        if ($console !== null) {
            $resolvedOptions = self::resolveOptions($container->get('config'));

            if (\class_exists(ConsoleFormatter::class) && \interface_exists(FormatterInterface::class)) {
                $console->add(new ServerLogCommand());
            }

            $documentRoot = null;
            $env          = null;

            if ($container->has(ConsoleKernelContract::class)) {
                $kernel = $container->get(ConsoleKernelContract::class);

                $documentRoot = $kernel->getRootDir() . '/' . \ltrim($resolvedOptions['webserver']['web_folder'], \DIRECTORY_SEPARATOR);
                $env          = $kernel->getEnvironment();
            }

            if (\class_exists(VarDumper::class)) {
                $console->add(new ServerDumpCommand($container->get(DumpServer::class)));
            }

            $console->addCommands([
                new ServerServeCommand($documentRoot, $env),
                new ServerStartCommand($documentRoot, $env),
                new ServerStatusCommand(),
                new ServerStopCommand(),
            ]);
        }

        return $console;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Events\Traits;

use Viserio\Component\Contract\Events\Exception\InvalidArgumentException;

trait ValidateNameTrait
{
    /**
     * The event name must only contain the characters A-Z, a-z, 0-9, _, and '.'.
     *
     * @param string $eventName
     *
     * @throws \Viserio\Component\Contract\Events\Exception\InvalidArgumentException
     *
     * @return void
     */
    protected function validateEventName(string $eventName): void
    {
        \preg_match_all('/([a-zA-Z0-9_\\.]+)/', $eventName, $matches);

        if (\count($matches[0]) >= 2) {
            throw new InvalidArgumentException(
                'The event name must only contain the characters A-Z, a-z, 0-9, _, and \'.\'.'
            );
        }
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Mail;

use Closure;
use Opis\Closure\SerializableClosure;
use Swift_Mailer;
use Viserio\Component\Contract\Mail\QueueMailer as QueueMailerContract;
use Viserio\Component\Contract\Queue\Job as JobContract;
use Viserio\Component\Contract\Queue\QueueConnector as QueueConnectorContract;

class QueueMailer extends Mailer implements QueueMailerContract
{
    /**
     * Queue instance.
     *
     * @var \Viserio\Component\Contract\Queue\QueueConnector
     */
    protected $queue;

    /**
     * Create a new Mailer instance.
     *
     * @param \Swift_Mailer                                    $swiftMailer
     * @param \Viserio\Component\Contract\Queue\QueueConnector $queue
     * @param array                                            $config
     */
    public function __construct(Swift_Mailer $swiftMailer, QueueConnectorContract $queue, array $config)
    {
        parent::__construct($swiftMailer, $config);

        $this->queue = $queue;
    }

    /**
     * {@inheritdoc}
     */
    public function getQueue(): QueueConnectorContract
    {
        return $this->queue;
    }

    /**
     * {@inheritdoc}
     */
    public function queue($view, array $data = [], $callback = null, string $queue = null)
    {
        $callback = $this->buildQueueCallable($callback);

        return $this->queue->push(
            'mailer@handleQueuedMessage',
            \compact('view', 'data', 'callback'),
            $queue
        );
    }

    /**
     * {@inheritdoc}
     */
    public function onQueue(string $queue, $view, array $data, $callback)
    {
        return $this->queue($view, $data, $callback, $queue);
    }

    /**
     * {@inheritdoc}
     */
    public function later(
        int $delay,
        $view,
        array $data   = [],
        $callback     = null,
        string $queue = null
    ) {
        if ($callback !== null) {
            $callback = $this->buildQueueCallable($callback);
        }

        return $this->queue->later(
            $delay,
            'mailer@handleQueuedMessage',
            \compact('view', 'data', 'callback'),
            $queue
        );
    }

    /**
     * {@inheritdoc}
     */
    public function laterOn(
        string $queue,
        int $delay,
        $view,
        array $data,
        $callback
    ) {
        return $this->later($delay, $view, $data, $callback, $queue);
    }

    /**
     * {@inheritdoc}
     */
    public function handleQueuedMessage(JobContract $job, array $data): void
    {
        $this->send($data['view'], $data['data'], $this->getQueuedCallable($data));

        $job->delete();
    }

    /**
     * Build the callable for a queued e-mail job.
     *
     * @param \Closure|string $callback
     *
     * @return \Closure|\Opis\Closure\SerializableClosure|string
     */
    protected function buildQueueCallable($callback)
    {
        if (! $callback instanceof Closure) {
            return $callback;
        }

        return new SerializableClosure($callback);
    }

    /**
     * Get the true callable for a queued e-mail message.
     *
     * @param array $data
     *
     * @return \Closure|string
     */
    protected function getQueuedCallable(array $data)
    {
        if (\mb_strpos($data['callback'], 'SerializableClosure') !== false) {
            return \unserialize($data['callback']);
        }

        return $data['callback'];
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Support\Traits;

use InvalidArgumentException;
use OutOfBoundsException;

trait BytesFormatTrait
{
    /**
     * Convert a number string to bytes.
     *
     * @param string $number
     *
     * @throws \InvalidArgumentException
     * @throws \OutOfBoundsException
     *
     * @return int limit in bytes or -1 if it's unlimited
     */
    protected static function convertToBytes(string $number): int
    {
        /**
         * Prefixes to specify unit of measure for memory amount.
         *
         * Warning: it is important to maintain the exact order of letters in this literal,
         * as it is used to convert string with units to bytes
         */
        $memoryUnits = 'BKMGTPE';

        if (\preg_match('/^(.*\d)\h*(\D)$/', $number, $matches) !== 1) {
            throw new InvalidArgumentException("Number format '{$number}' is not recognized.");
        }

        $unitSymbol = \mb_strtoupper($matches[2]);

        if (\mb_strpos($memoryUnits, $unitSymbol) === false) {
            throw new InvalidArgumentException("The number '{$number}' has an unrecognized unit: '{$unitSymbol}'.");
        }

        $result = self::convertToNumber($matches[1]);
        $pow    = $unitSymbol ? \mb_strpos($memoryUnits, $unitSymbol) : 0;

        if (\PHP_INT_SIZE <= 4 && $pow >= 4) {
            throw new OutOfBoundsException('A 32-bit system is unable to process such a number.');
        }

        if ($unitSymbol) {
            $result *= 1024 ** $pow;
        }

        return (int) $result;
    }

    /**
     * Remove non-numeric characters in the string to cast it to a numeric value.
     *
     * Incoming number can be presented in arbitrary format that depends on locale. We don't possess locale information.
     * So the best can be done is to treat number as an integer and eliminate delimiters.
     * Method will not behave correctly with non-integer numbers for the following reason:
     * - if value has more than one delimiter, such as in French notation: "1 234,56" -- then we can infer decimal part
     * - but the value has only one delimiter, such as "234,56", then it is impossible to know whether it is decimal
     *   separator or not. Only knowing the right format would allow this.
     *
     * @param string $number
     *
     * @throws \InvalidArgumentException
     *
     * @return string
     */
    private static function convertToNumber(string $number): string
    {
        \preg_match_all('/(\D+)/', $number, $matches);

        if (\count(\array_unique($matches[0])) > 1) {
            throw new InvalidArgumentException(
                "The number '{$number}' seems to have decimal part. Only integer numbers are supported."
            );
        }

        return \preg_replace('/\D+/', '', $number);
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Bridge\Twig\Extension;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Viserio\Component\Contract\Config\Repository as RepositoryContract;
use Viserio\Component\Contract\Config\Traits\ConfigAwareTrait;

class ConfigExtension extends AbstractExtension
{
    use ConfigAwareTrait;

    /**
     * Create a new config extension.
     *
     * @param \Viserio\Component\Contract\Config\Repository $config
     */
    public function __construct(RepositoryContract $config)
    {
        $this->config = $config;
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        return 'Viserio_Bridge_Twig_Extension_Config';
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions(): array
    {
        return [
            new TwigFunction('config', [$this->config, 'get']),
            new TwigFunction('config_get', [$this->config, 'get']),
            new TwigFunction('config_has', [$this->config, 'has']),
        ];
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Foundation\Bootstrap;

use Viserio\Component\Contract\Foundation\Bootstrap as BootstrapContract;
use Viserio\Component\Contract\Foundation\Kernel as KernelContract;

class LoadServiceProvider implements BootstrapContract
{
    /**
     * {@inheritdoc}
     */
    public static function getPriority(): int
    {
        return 64;
    }

    /**
     * {@inheritdoc}
     */
    public static function bootstrap(KernelContract $kernel): void
    {
        $container = $kernel->getContainer();

        foreach ($kernel->registerServiceProviders() as $provider) {
            $container->register($container->resolve($provider));
        }
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Manager;

use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Manager\ConnectionManager as ConnectionManagerContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\OptionsResolver\RequiresMandatoryOptions as RequiresMandatoryOptionsContract;
use Viserio\Component\Manager\Traits\ManagerTrait;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

abstract class AbstractConnectionManager implements
    RequiresComponentConfigContract,
    RequiresMandatoryOptionsContract,
    ConnectionManagerContract
{
    use ContainerAwareTrait;
    use OptionsResolverTrait;
    use ManagerTrait;

    /**
     * Default name for the connections config.
     *
     * @var string
     */
    protected const CONFIG_LIST_NAME = 'connections';

    /**
     * The active connection instances.
     *
     * @var array
     */
    protected $connections = [];

    /**
     * Create a new connection manager instance.
     *
     * @param array|\ArrayAccess $config
     */
    public function __construct($config)
    {
        $this->resolvedOptions = self::resolveOptions($config);
    }

    /**
     * Dynamically pass methods to the default connection.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @return mixed
     */
    public function __call(string $method, array $parameters)
    {
        return $this->getConnection()->{$method}(...$parameters);
    }

    /**
     * {@inheritdoc}
     */
    public function getConnections(): array
    {
        return $this->connections;
    }

    /**
     * {@inheritdoc}
     */
    public function getConnection(?string $name = null)
    {
        $name = $name ?? $this->getDefaultConnection();

        // If the given connection has not been created before, we will create the instances
        // here and cache it so we can return it next time very quickly. If there is
        // already a connection created by this name, we'll just return that instance.
        if (! isset($this->connections[$name])) {
            $this->connections[$name] = $this->createConnection(
                $this->getConnectionConfig($name)
            );
        }

        return $this->connections[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function reconnect(string $name = null): object
    {
        $name = $name ?? $this->getDefaultConnection();

        $this->disconnect($name);

        return $this->getConnection($name);
    }

    /**
     * {@inheritdoc}
     */
    public function disconnect(string $name = null): void
    {
        $name = $name ?? $this->getDefaultConnection();

        unset($this->connections[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultConnection(): string
    {
        return $this->resolvedOptions['default'];
    }

    /**
     * {@inheritdoc}
     */
    public function setDefaultConnection(string $name): void
    {
        $this->resolvedOptions['default'] = $name;
    }

    /**
     * {@inheritdoc}
     */
    public function hasConnection(string $connect): bool
    {
        $method = 'create' . static::studly($connect) . 'Connection';

        return \method_exists($this, $method) || isset($this->extensions[$connect]);
    }

    /**
     * {@inheritdoc}
     */
    public function getConnectionConfig(string $name): array
    {
        $name = $name ?? $this->getDefaultConnection();

        return $this->getConfigFromName($name);
    }

    /**
     * {@inheritdoc}
     */
    public function createConnection(array $config)
    {
        $method = 'create' . static::studly($config['name']) . 'Connection';

        return $this->create($config, $method, 'Connection [%s] is not supported.');
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\OptionsResolver\Provider;

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Console\Application;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\OptionsResolver\Command\OptionDumpCommand;
use Viserio\Component\OptionsResolver\Command\OptionReaderCommand;

class ConsoleCommandsServiceProvider implements
    ServiceProviderInterface,
    RequiresComponentConfigContract,
    ProvidesDefaultOptionsContract
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [
            Application::class => [self::class, 'extendConsole'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDimensions(): array
    {
        return ['viserio', 'console'];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDefaultOptions(): array
    {
        return [
            'lazily_commands' => [
                'option:dump' => OptionDumpCommand::class,
                'option:read' => OptionReaderCommand::class,
            ],
        ];
    }

    /**
     * Extend viserio console with commands.
     *
     * @param \Psr\Container\ContainerInterface           $container
     * @param null|\Viserio\Component\Console\Application $console
     *
     * @return null|\Viserio\Component\Console\Application
     */
    public static function extendConsole(ContainerInterface $container, ?Application $console = null): ?Application
    {
        if ($console !== null) {
            // @var Application $console
            $console->addCommands([
                new OptionDumpCommand(),
                new OptionReaderCommand(),
            ]);
        }

        return $console;
    }
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Manager\Traits;

use Closure;
use Viserio\Component\Contract\Manager\Exception\InvalidArgumentException;

/** @internal */
trait ManagerTrait
{
    /**
     * The cache of studly-cased words.
     *
     * @var array
     */
    protected static $studlyCache = [];

    /**
     * The registered custom driver / connections creators.
     *
     * @var array
     */
    protected $extensions = [];

    /**
     * Resolved options.
     *
     * @var array
     */
    protected $resolvedOptions = [];

    /**
     * {@inheritdoc}
     */
    public static function getDimensions(): array
    {
        return ['viserio', static::getConfigName()];
    }

    /**
     * {@inheritdoc}
     */
    public static function getMandatoryOptions(): array
    {
        return [static::CONFIG_LIST_NAME];
    }

    /**
     * {@inheritdoc}
     */
    public function getConfig(): array
    {
        return $this->resolvedOptions;
    }

    /**
     * {@inheritdoc}
     */
    public function extend(string $driver, Closure $callback): void
    {
        $this->extensions[$driver] = $callback->bindTo($this, $this);
    }

    /**
     * Call a custom connection / driver creator.
     *
     * @param string $extension
     * @param array  $config
     *
     * @return mixed
     */
    protected function callCustomCreator(string $extension, array $config = [])
    {
        return $this->extensions[$extension]($config);
    }

    /**
     * Get config on adapter name.
     *
     * @param string $name
     *
     * @return array
     */
    protected function getConfigFromName(string $name): array
    {
        $adapter = $this->resolvedOptions[static::CONFIG_LIST_NAME] ?? [];

        if (isset($adapter[$name]) && \is_array($adapter[$name])) {
            $config         = $adapter[$name];
            $config['name'] = $name;

            return $config;
        }

        return ['name' => $name];
    }

    /**
     * Make a new driver instance.
     *
     * @param array  $config
     * @param string $method
     * @param string $errorMessage
     *
     * @throws \Viserio\Component\Contract\Manager\Exception\InvalidArgumentException
     *
     * @return mixed
     */
    protected function create(array $config, string $method, string $errorMessage)
    {
        if (isset($this->extensions[$config['name']])) {
            return $this->callCustomCreator($config['name'], $config);
        }

        if (\method_exists($this, $method)) {
            return $this->{$method}($config);
        }

        throw new InvalidArgumentException(\sprintf($errorMessage, $config['name']));
    }

    /**
     * Convert a value to studly caps case.
     *
     * @param string $value
     *
     * @return string
     */
    protected static function studly(string $value): string
    {
        $key = $value;

        if (isset(static::$studlyCache[$key])) {
            return static::$studlyCache[$key];
        }

        if (\ctype_upper(\str_replace(['_', '-'], '', $value))) {
            $value = \mb_strtolower($value);
        }

        $value = \ucwords(\str_replace(['-', '_'], ' ', $value));

        return static::$studlyCache[$key] = \str_replace(' ', '', $value);
    }

    /**
     * Get the configuration name.
     *
     * @return string
     */
    abstract protected static function getConfigName(): string;
}


<?php
declare(strict_types=1);
namespace Viserio\Component\Translation\Extractor;

use Viserio\Component\Contract\Translation\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Translation\Extractor as ExtractorContract;

class ChainExtractor implements ExtractorContract
{
    /**
     * The extractors.
     *
     * @var \Viserio\Component\Contract\Translation\Extractor[]
     */
    private $extractors = [];

    /**
     * Adds a loader to the translation extractor.
     *
     * @param string                                            $format
     * @param \Viserio\Component\Contract\Translation\Extractor $extractor
     *
     * @return void
     */
    public function addExtractor(string $format, ExtractorContract $extractor): void
    {
        $this->extractors[$format] = $extractor;
    }

    /**
     * {@inheritdoc}
     */
    public function setPrefix(string $prefix): void
    {
        foreach ($this->extractors as $extractor) {
            $extractor->setPrefix($prefix);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function extract($resource): array
    {
        if (! \is_string($resource) && ! \is_array($resource)) {
            throw new InvalidArgumentException(\sprintf(
                'The resource parameter must be of type string or array, [%s] given.',
                \is_object($resource) ? \get_class($resource) : \gettype($resource)
            ));
        }

        $messages = [];

        foreach ($this->extractors as $extractor) {
            foreach ($extractor->extract($resource) as $key => $foundMessages) {
                $messages[$key] = $foundMessages;
            }
        }

        return $messages;
    }
}