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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;

class TimeDataCollector extends AbstractDataCollector
{
    /**
     * The request start time.
     *
     * @var float
     */
    protected $requestStartTime;

    /**
     * The request end time.
     *
     * @var float
     */
    protected $requestEndTime;

    /**
     * Collection of started measures.
     *
     * @var array
     */
    protected $startedMeasures = [];

    /**
     * Collection of measures.
     *
     * @var array
     */
    protected $measures = [];

    /**
     * Create new time collector instance.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $serverRequest
     */
    public function __construct(ServerRequestInterface $serverRequest)
    {
        $time = \microtime(true);

        if (($requestTimeFloat = $serverRequest->getHeaderLine('request_time_float')) !== '') {
            $time = $requestTimeFloat;
        } elseif (($requestTime = $serverRequest->getHeaderLine('request_time')) !== '') {
            $time = $requestTime;
        }

        $this->requestStartTime = (float) $time;
    }

    /**
     * Returns the request start time.
     *
     * @return float
     *
     * @codeCoverageIgnore
     */
    public function getRequestStartTime(): float
    {
        return $this->requestStartTime;
    }

    /**
     * Returns the request end time.
     *
     * @return float
     *
     * @codeCoverageIgnore
     */
    public function getRequestEndTime(): float
    {
        return $this->requestEndTime;
    }

    /**
     * Returns an array of all measures.
     *
     * @return array
     */
    public function getMeasures(): array
    {
        return $this->measures;
    }

    /**
     * {@inheritdoc}
     */
    public function collect(ServerRequestInterface $serverRequest, ResponseInterface $response): void
    {
        $this->requestEndTime = \microtime(true);

        foreach (\array_keys($this->startedMeasures) as $name) {
            $this->stopMeasure($name);
        }

        \usort($this->measures, static function ($a, $b) {
            if ($a['start'] === $b['start']) {
                return 0;
            }

            return $a['start'] < $b['start'] ? -1 : 1;
        });

        $this->data = [
            'start'        => $this->requestStartTime,
            'end'          => $this->requestEndTime,
            'duration'     => $this->getRequestDuration(),
            'duration_str' => $this->formatDuration($this->getRequestDuration()),
            'measures'     => $this->measures,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getMenu(): array
    {
        return [
            'icon'  => 'ic_schedule_white_24px.svg',
            'label' => '',
            'value' => $this->data['duration_str'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getMenuPosition(): string
    {
        return 'right';
    }

    /**
     * Returns the duration of a request.
     *
     * @return float
     */
    public function getRequestDuration(): float
    {
        if ($this->requestEndTime !== null) {
            return $this->requestEndTime - $this->requestStartTime;
        }

        return \microtime(true) - $this->requestStartTime;
    }

    /**
     * Starts a measure.
     *
     * @param string      $name      Internal name, used to stop the measure
     * @param null|string $label     Public name
     * @param null|string $collector The source of the collector
     *
     * @return void
     */
    public function startMeasure(string $name, string $label = null, string $collector = null): void
    {
        $start = \microtime(true);

        $this->startedMeasures[$name] = [
            'label'     => $label ?? $name,
            'start'     => $start,
            'collector' => $collector,
        ];
    }

    /**
     * Check a measure exists.
     *
     * @param string $name
     *
     * @return bool
     */
    public function hasStartedMeasure(string $name): bool
    {
        return isset($this->startedMeasures[$name]);
    }

    /**
     * Stops a measure.
     *
     * @param string $name
     * @param array  $params
     *
     * @throws \RuntimeException
     *
     * @return void
     */
    public function stopMeasure(string $name, array $params = []): void
    {
        $end = \microtime(true);

        if (! $this->hasStartedMeasure($name)) {
            throw new RuntimeException(\sprintf(
                'Failed stopping measure [%s] because it hasn\'t been started.',
                $name
            ));
        }

        $this->addMeasure(
            $this->startedMeasures[$name]['label'],
            $this->startedMeasures[$name]['start'],
            $end,
            $params,
            $this->startedMeasures[$name]['collector']
        );

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

    /**
     * Adds a measure.
     *
     * @param string      $label
     * @param float       $start
     * @param float       $end
     * @param array       $params
     * @param null|string $collector
     *
     * @return void
     */
    public function addMeasure(
        string $label,
        float $start,
        float $end,
        array $params      = [],
        ?string $collector = null
    ): void {
        $this->measures[] = [
            'label'          => $label,
            'start'          => $start,
            'relative_start' => $start - $this->requestStartTime,
            'end'            => $end,
            'relative_end'   => $end - $this->requestEndTime,
            'duration'       => $end - $start,
            'duration_str'   => $this->formatDuration($end - $start),
            'params'         => $params,
            'collector'      => $collector,
        ];
    }
}


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

use Throwable;

class ExceptionIdentifier
{
    /**
     * The identification mappings.
     *
     * Note that for performance reasons, we only store up to 16 identifications in
     * memory at a given time.
     *
     * @var string[]
     */
    private static $identification = [];

    /**
     * Identify the given exception.
     *
     * @param \Throwable $exception
     *
     * @return string
     */
    public static function identify(Throwable $exception): string
    {
        $hash = \spl_object_hash($exception);

        // if we know about the exception, return it's id
        if (isset(self::$identification[$hash])) {
            return self::$identification[$hash];
        }

        // cleanup in preparation for the identification
        if (\count((array) self::$identification) >= 16) {
            \array_shift(self::$identification);
        }

        // generate, store, and return the id
        return self::$identification[$hash] = self::uuid4();
    }

    /**
     * Generate v4 UUID.
     *
     * We're generating uuids according to the official v4 spec.
     *
     * @return string
     */
    private static function uuid4(): string
    {
        $hash   = \bin2hex(\random_bytes(16));
        $timeHi = \hexdec(\substr($hash, 12, 4)) & 0x0fff;
        $timeHi &= ~0xf000;
        $timeHi |= 4 << 12;

        $clockSeqHi = \hexdec(\substr($hash, 16, 2)) & 0x3f;
        $clockSeqHi &= ~0xc0;
        $clockSeqHi |= 0x80;

        $params = [\substr($hash, 0, 8), \substr($hash, 8, 4), \sprintf('%04x', $timeHi), \sprintf('%02x', $clockSeqHi), \substr($hash, 18, 2), \substr($hash, 20, 12)];

        return \vsprintf('%08s-%04s-%04s-%02s%02s-%012s', $params);
    }
}


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

class GroupParser extends Parser
{
    /**
     * Key for grouping.
     *
     * @var string
     */
    private $groupKey;

    /**
     * Set group key.
     *
     * @param string $key
     *
     * @return $this
     */
    public function setGroup(string $key): self
    {
        $this->groupKey = $key;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        if (! $this->groupKey) {
            // @var $method self
            return parent::parse($payload);
        }

        return [$this->groupKey => parent::parse($payload)];
    }
}


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

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
use Viserio\Component\Contract\Http\Exception\InvalidArgumentException;

abstract class AbstractMessage implements MessageInterface
{
    /**
     * Protocol version.
     *
     * @var string
     */
    protected $protocol = '1.1';

    /**
     * Map of all registered headers, as original name => array of values.
     *
     * @var array<string,string>
     */
    protected $headers = [];

    /**
     * Map of lowercase header name => original name at registration.
     *
     * @var array<string,string>
     */
    protected $headerNames = [];

    /**
     * A stream instance.
     *
     * @var \Psr\Http\Message\StreamInterface
     */
    protected $stream;

    /**
     * A map of valid protocol versions.
     *
     * @var array<string,int>
     */
    private static $validProtocolVersions = [
        '1.0' => true,
        '1.1' => true,
        '2.0' => true,
    ];

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

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

    /**
     * {@inheritdoc}
     */
    public function withProtocolVersion($version): self
    {
        $this->validateProtocolVersion($version);

        if ($this->protocol === $version) {
            return $this;
        }

        $new           = clone $this;
        $new->protocol = $version;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function hasHeader($header): bool
    {
        return isset($this->headerNames[\strtolower($header)]);
    }

    /**
     * {@inheritdoc}
     */
    public function getHeader($header): array
    {
        if (! $this->hasHeader($header)) {
            return [];
        }

        $header = \strtolower($header);
        $header = $this->headerNames[$header];
        $value  = $this->headers[$header];

        return \is_array($value) ? $value : [$value];
    }

    /**
     * {@inheritdoc}
     */
    public function getHeaderLine($name): string
    {
        $value = $this->getHeader($name);

        if (empty($value)) {
            return '';
        }

        return \implode(',', $value);
    }

    /**
     * {@inheritdoc}
     */
    public function withHeader($header, $value)
    {
        if (! \is_string($header)) {
            throw new InvalidArgumentException(\sprintf(
                'Invalid header name type; expected string; received [%s]',
                (\is_object($header) ? \get_class($header) : \gettype($header))
            ));
        }

        HeaderSecurity::assertValidName($header);

        $header     = \trim($header);
        $normalized = \strtolower($header);
        $new        = clone $this;

        // Remove the header lines.
        if ($new->hasHeader($header)) {
            unset($new->headers[$new->headerNames[$normalized]]);
        }

        // Add the header line.
        $new->headerNames[$normalized] = $header;
        $new->headers[$header]         = $this->filterHeaderValue($value);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withAddedHeader($header, $value): self
    {
        if (! \is_string($header)) {
            throw new InvalidArgumentException(\sprintf(
                'Invalid header name type; expected string; received [%s]',
                (\is_object($header) ? \get_class($header) : \gettype($header))
            ));
        }

        if (! $this->hasHeader($header)) {
            return $this->withHeader($header, $value);
        }

        HeaderSecurity::assertValidName($header);

        $header = \trim($header);
        $header = $this->headerNames[\strtolower($header)];

        $new                   = clone $this;
        $value                 = $this->filterHeaderValue($value);
        $new->headers[$header] = \array_merge($this->headers[$header], $value);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withoutHeader($header): self
    {
        $normalized = \strtolower($header);

        if (! isset($this->headerNames[$normalized])) {
            return $this;
        }

        $header = $this->headerNames[$normalized];
        $new    = clone $this;

        unset($new->headers[$header], $new->headerNames[$normalized]);

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getBody(): StreamInterface
    {
        if ($this->stream === null) {
            $this->stream = new Stream(Util::tryFopen('php://temp', 'r+b'));
        }

        return $this->stream;
    }

    /**
     * {@inheritdoc}
     */
    public function withBody(StreamInterface $body): self
    {
        if ($body === $this->stream) {
            return $this;
        }

        $new         = clone $this;
        $new->stream = $body;

        return $new;
    }

    /**
     * Set validated headers.
     *
     * @param array $headers
     *
     * @return void
     */
    protected function setHeaders(array $headers): void
    {
        if (\count($headers) === 0) {
            return;
        }

        $this->headerNames = $this->headers = [];

        foreach ($headers as $header => $value) {
            $value      = $this->filterHeaderValue($value);
            $normalized = \strtolower($header);

            if (isset($this->headerNames[$normalized])) {
                $header                 = (string) $this->headerNames[$normalized];
                $this->headers[$header] += $value;
            } else {
                $this->headerNames[$normalized] = $header;
                $this->headers[$header]         = $value;
            }
        }
    }

    /**
     * Validate the HTTP protocol version.
     *
     * @param string $version
     *
     * @throws \Viserio\Component\Contract\Http\Exception\InvalidArgumentException on invalid HTTP protocol version
     *
     * @return void
     */
    private function validateProtocolVersion(string $version): void
    {
        if ($version === '') {
            throw new InvalidArgumentException('HTTP protocol version can not be empty.');
        }

        if (! isset(self::$validProtocolVersions[$version])) {
            throw new InvalidArgumentException(
                \sprintf('Invalid HTTP version. Must be one of: [%s].', \implode(', ', \array_keys(self::$validProtocolVersions)))
            );
        }
    }

    /**
     * Test that an array contains only strings.
     *
     * @param array $array
     *
     * @return bool
     */
    private function arrayContainsOnlyStrings(array $array): bool
    {
        // Test if a value is a string.
        $filterStringValue = static function (bool $carry, $item) {
            if (! \is_string($item)) {
                return false;
            }

            return $carry;
        };

        return \array_reduce($array, $filterStringValue, true);
    }

    /**
     * Filter array headers.
     *
     * @param array|string $values
     *
     * @return array
     */
    private function filterHeaderValue($values): array
    {
        if (! \is_array($values)) {
            $values = [$values];
        }

        if (\count($values) === 0 || ! $this->arrayContainsOnlyStrings($values)) {
            throw new InvalidArgumentException(
                'Invalid header value: must be a string or array of strings and cannot be an empty array.'
            );
        }

        $values = \array_map(static function ($value) {
            // @see http://tools.ietf.org/html/rfc7230#section-3.2
            HeaderSecurity::assertValid($value);

            $value = (string) $value;

            // Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
            //
            // header-field = field-name ":" OWS field-value OWS
            // OWS          = *( SP / HTAB )
            //
            // @see https://tools.ietf.org/html/rfc7230#section-3.2.4
            return \trim($value, " \t");
        }, \array_values($values));

        return \array_map([HeaderSecurity::class, 'filter'], \array_values($values));
    }
}


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

use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;

final class RequestContextProvider implements ContextProviderInterface
{
    /**
     * A server request implantation.
     *
     * @var \Psr\Http\Message\ServerRequestInterface
     */
    private $currentRequest;

    /**
     * Created a RequestContextProvider instance.
     *
     * @param \Psr\Http\Message\ServerRequestInterface $currentRequest
     */
    public function __construct(ServerRequestInterface $currentRequest)
    {
        $this->currentRequest = $currentRequest;
    }

    /**
     * {@inheritdoc}
     */
    public function getContext(): ?array
    {
        return [
            'uri'        => (string) $this->currentRequest->getUri(),
            'method'     => $this->currentRequest->getMethod(),
            'identifier' => \spl_object_hash($this->currentRequest),
        ];
    }
}


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

use Cake\Chronos\Chronos;
use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\Console\Kernel as ConsoleKernelContract;

class DownCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'app:down';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'app:down
        [--message= : The message for the maintenance mode.]
        [--retry= : The number of seconds after which the request may be retried.]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Put the application into maintenance mode';

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        $kernel = $this->getContainer()->get(ConsoleKernelContract::class);

        \file_put_contents(
            $kernel->getStoragePath('framework' . \DIRECTORY_SEPARATOR . 'down'),
            \json_encode($this->getDownPayload(), \JSON_PRETTY_PRINT)
        );

        $this->comment('Application is now in maintenance mode.');

        return 0;
    }

    /**
     * Get the information that are put into the "down" file.
     *
     * @return array
     */
    protected function getDownPayload(): array
    {
        return [
            'time'    => Chronos::now()->getTimestamp(),
            'message' => $this->option('message'),
            'retry'   => $this->getRetryTime(),
        ];
    }

    /**
     * Get the number of seconds the client should wait before retrying their request.
     *
     * @return null|int
     */
    protected function getRetryTime(): ?int
    {
        $retry = $this->option('retry');

        return \is_numeric($retry) && $retry > 0 ? (int) $retry : null;
    }
}


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use ReflectionClass;
use Viserio\Component\Contract\Profiler\PanelAware as PanelAwareContract;
use Viserio\Component\Contract\Profiler\TooltipAware as TooltipAwareContract;
use Viserio\Component\Profiler\DataCollector\AbstractDataCollector;

/**
 * Ported from phpcache, see original.
 *
 * @see https://github.com/php-cache/cache-bundle/blob/master/src/DataCollector/CacheDataCollector.php
 */
class Psr6Psr16CacheDataCollector extends AbstractDataCollector implements
    TooltipAwareContract,
    PanelAwareContract
{
    /**
     * Collection of TraceableCacheItemDecorater.
     *
     * @var array
     */
    private $pools = [];

    /**
     * Create a new cache data collector.
     *
     * @param \Viserio\Component\Profiler\DataCollector\Bridge\Cache\CacheDecorator $cache
     *
     * @throws \Viserio\Component\Contract\Profiler\Exception\UnexpectedValueException
     *
     * @return void
     */
    public function addPool(CacheDecorator $cache): void
    {
        $this->pools[$cache->getName()] = $cache;
    }

    /**
     * {@inheritdoc}
     */
    public function collect(ServerRequestInterface $serverRequest, ResponseInterface $response): void
    {
        $empty = [
            'calls'      => [],
            'config'     => [],
            'options'    => [],
            'statistics' => [],
        ];

        $this->data = ['pools' => $empty, 'total' => $empty];

        foreach ($this->pools as $name => $instance) {
            $this->data['pools']['calls'][$name] = $instance->getCalls();
        }

        $this->data['pools']['statistics'] = $this->calculateStatistics();
        $this->data['total']['statistics'] = $this->calculateTotalStatistics();
    }

    /**
     * {@inheritdoc}
     */
    public function getMenu(): array
    {
        $static = $this->data['total']['statistics'];

        return [
            'icon'  => 'ic_layers_white_24px.svg',
            'label' => $static['calls'] . ' in',
            'value' => $this->formatDuration($static['time']),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getTooltip(): string
    {
        $static = $this->data['total']['statistics'];

        return $this->createTooltipGroup([
            'Cache calls'  => $static['calls'],
            'Total time'   => $this->formatDuration($static['time']),
            'Cache hits'   => $static['hits'],
            'Cache writes' => $static['writes'],
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getPanel(): string
    {
        $data = [];

        foreach ($this->data['pools']['calls'] as $name => $calls) {
            $html              = '';
            $statistic         = $this->data['pools']['statistics'][$name];
            $statistic['time'] = $this->formatDuration($statistic['time']);

            $html .= $this->createMetrics(
                $statistic,
                'Statistics'
            );

            $calledCalls = [];

            foreach ($calls as $i => $call) {
                $calledCalls[] = [
                    $this->formatDuration($call->end - $call->start),
                    $call->name,
                    $call->result,
                ];
            }

            $html .= $this->createTable(
                $calledCalls,
                [
                    'name'    => 'Calls',
                    'headers' => ['Time', 'Call', 'Hit'],
                ]
            );

            $data[] = [
                'name'    => (new ReflectionClass($name))->getShortName(),
                'content' => $html,
            ];
        }

        return $this->createTabs($data);
    }

    /**
     * Method returns amount of logged Cache reads: "get" calls.
     *
     * @return array
     */
    public function getStatistics(): array
    {
        return $this->data['pools']['statistics'];
    }

    /**
     * Method returns the statistic totals.
     *
     * @return array
     */
    public function getTotals(): array
    {
        return $this->data['total']['statistics'];
    }

    /**
     * Method returns all logged Cache call objects.
     *
     * @return int
     */
    public function getCalls(): int
    {
        return $this->data['pools']['calls'];
    }

    /**
     * {@inheritdoc}
     */
    public function reset(): void
    {
        parent::reset();

        foreach ($this->pools as $instance) {
            // Calling getCalls() will clear the calls.
            $instance->getCalls();
        }
    }

    /**
     * @return array
     */
    private function calculateStatistics(): array
    {
        $statistics = [];

        foreach ($this->data['pools']['calls'] as $name => $calls) {
            $statistics[$name] = [
                'calls'   => 0,
                'time'    => 0,
                'reads'   => 0,
                'hits'    => 0,
                'misses'  => 0,
                'writes'  => 0,
                'deletes' => 0,
            ];

            foreach ($calls as $call) {
                $statistics[$name]['calls']++;
                $statistics[$name]['time']  += $call->end - $call->start;

                if ($call->name === 'getItem') {
                    $statistics[$name]['reads']++;

                    if ($call->hits) {
                        $statistics[$name]['hits']++;
                    } else {
                        $statistics[$name]['misses']++;
                    }
                } elseif ($call->name === 'getItems') {
                    $count = $call->hits + $call->misses;
                    $statistics[$name]['reads']  += $count;
                    $statistics[$name]['hits']   += $call->hits;
                    $statistics[$name]['misses'] += $count - $call->misses;
                } elseif ($call->name === 'hasItem') {
                    $statistics[$name]['reads']++;

                    if ($call->result === false) {
                        $statistics[$name]['misses']++;
                    }
                } elseif ($call->name === 'save') {
                    $statistics[$name]['writes']++;
                } elseif ($call->name === 'deleteItem') {
                    $statistics[$name]['deletes']++;
                }
            }

            if ($statistics[$name]['reads']) {
                $statistics[$name]['hits'] = \round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2) . '%';
            } else {
                $statistics[$name]['hits'] = 0;
            }
        }

        return $statistics;
    }

    /**
     * @return array
     */
    private function calculateTotalStatistics(): array
    {
        $statistics = $this->getStatistics();
        $totals     = [
            'calls'  => 0,
            'time'   => 0,
            'reads'  => 0,
            'hits'   => 0,
            'misses' => 0,
            'writes' => 0,
        ];

        foreach ($statistics as $name => $values) {
            foreach ($totals as $key => $value) {
                $totals[$key] += $statistics[$name][$key];
            }
        }

        if ($totals['reads']) {
            $totals['hits'] = \round(100 * $totals['hits'] / $totals['reads'], 2) . '%';
        } else {
            $totals['hits'] = 0;
        }

        return $totals;
    }
}


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

use Viserio\Component\Support\Invoker;

trait InvokerAwareTrait
{
    /**
     * The invoker instance.
     *
     * @var \Viserio\Component\Support\Invoker
     */
    protected $invoker;

    /**
     * Get configured invoker.
     *
     * @return \Viserio\Component\Support\Invoker
     */
    protected function getInvoker(): Invoker
    {
        if ($this->invoker === null) {
            $this->invoker = new Invoker();

            if ($this->container !== null) {
                $this->invoker->setContainer($this->container)
                    ->injectByTypeHint(true)
                    ->injectByParameterName(true);
            }
        }

        return $this->invoker;
    }
}


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

use InvalidArgumentException as BaseInvalidArgumentException;

class InvalidArgumentException extends BaseInvalidArgumentException implements Exception
{
    /**
     * When an invalid type is encountered.
     *
     * @param string $name        name of the value being validated
     * @param mixed  $provided    the provided value
     * @param array  $expected    the expected value type
     * @param string $configClass
     *
     * @return self
     */
    public static function invalidType(string $name, $provided, array $expected, $configClass): self
    {
        return new self(\sprintf(
            'Invalid configuration value provided for [%s]; Expected [%s], but got [%s], in [%s].',
            $name,
            \count($expected) === 1 ? $expected[0] : \implode('] or [', $expected),
            (\is_object($provided) ? \get_class($provided) : \gettype($provided)),
            $configClass
        ));
    }
}


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

use Cache\Adapter\Common\PhpCachePool as PhpCachePoolInterface;
use Interop\Container\ServiceProviderInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Container\ContainerInterface;
use Psr\SimpleCache\CacheInterface;
use Viserio\Component\Contract\Profiler\Profiler as ProfilerContract;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\PhpCacheTraceableCacheDecorator;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\Psr6Psr16CacheDataCollector;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\SimpleTraceableCacheDecorator;
use Viserio\Component\Profiler\DataCollector\Bridge\Cache\TraceableCacheItemDecorator;

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

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [
            CacheItemPoolInterface::class => [self::class, 'extendCacheItemPool'],
            CacheInterface::class         => [self::class, 'extendSimpleTraceableCache'],
            ProfilerContract::class       => [self::class, 'extendProfiler'],
        ];
    }

    /**
     * Decorate CacheItemPool instances.
     *
     * @param \Psr\Container\ContainerInterface      $container
     * @param null|\Psr\Cache\CacheItemPoolInterface $cache
     *
     * @return null|\Psr\Cache\CacheItemPoolInterface
     */
    public static function extendCacheItemPool(
        ContainerInterface $container,
        ?CacheItemPoolInterface $cache = null
    ): ?CacheItemPoolInterface {
        if ($cache !== null) {
            if ($cache instanceof PhpCachePoolInterface) {
                return new PhpCacheTraceableCacheDecorator($cache);
            }

            return new TraceableCacheItemDecorator($cache);
        }

        return $cache;
    }

    /**
     * Decorate SimpleTraceableCache instances.
     *
     * @param \Psr\Container\ContainerInterface    $container
     * @param null|\Psr\SimpleCache\CacheInterface $cache
     *
     * @return null|\Psr\SimpleCache\CacheInterface
     */
    public static function extendSimpleTraceableCache(
        ContainerInterface $container,
        ?CacheInterface $cache = null
    ): ?CacheInterface {
        if ($cache !== null) {
            if ($cache instanceof PhpCachePoolInterface) {
                return new PhpCacheTraceableCacheDecorator($cache);
            }

            return new SimpleTraceableCacheDecorator($cache);
        }

        return $cache;
    }

    /**
     * Extend viserio profiler with data collector.
     *
     * @param \Psr\Container\ContainerInterface                  $container
     * @param null|\Viserio\Component\Contract\Profiler\Profiler $profiler
     *
     * @return null|\Viserio\Component\Contract\Profiler\Profiler
     */
    public static function extendProfiler(
        ContainerInterface $container,
        ?ProfilerContract $profiler = null
    ): ?ProfilerContract {
        if ($profiler !== null) {
            $collector = new Psr6Psr16CacheDataCollector();

            if ($container->has(CacheItemPoolInterface::class)) {
                if (($cache = $container->get(CacheItemPoolInterface::class)) instanceof TraceableCacheItemDecorator) {
                    $collector->addPool($cache);
                }
            }

            if ($container->has(CacheInterface::class)) {
                $cache = $container->get(CacheInterface::class);

                if ($cache instanceof SimpleTraceableCacheDecorator || $cache instanceof PhpCacheTraceableCacheDecorator) {
                    $collector->addPool($cache);
                }
            }

            $profiler->addCollector($collector);
        }

        return $profiler;
    }
}


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

use Closure;

class Env
{
    /**
     * Private constructor; non-instantiable.
     *
     * @codeCoverageIgnore
     */
    private function __construct()
    {
    }

    /**
     * Gets the value of an environment variable. Supports boolean, empty, null and base64 prefix.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public static function get(string $key, $default = null)
    {
        $value = \getenv($key);

        if ($value === false) {
            return $default instanceof Closure ? $default() : $default;
        }

        if (\preg_match('/base64:|\'base64:|"base64:/', $value) === 1) {
            return \base64_decode(\mb_substr($value, 7), true);
        }

        if (\in_array(
            \mb_strtolower($value),
            [
                'false',
                '(false)',
                'true',
                '(true)',
                'yes',
                '(yes)',
                'no',
                '(no)',
                'on',
                '(on)',
                'off',
                '(off)',
            ],
            true
        )) {
            $value = \str_replace(['(', ')'], '', $value);

            return \filter_var(
                $value,
                \FILTER_VALIDATE_BOOLEAN,
                \FILTER_NULL_ON_FAILURE
            );
        }

        if ($value === 'null' || $value === '(null)') {
            return null;
        }

        if (\is_numeric($value)) {
            return $value + 0;
        }

        if ($value === 'empty' || $value === '(empty)') {
            return '';
        }

        if (\mb_strlen($value) > 1 &&
            \mb_substr($value, 0, \mb_strlen('"')) === '"' &&
            \mb_substr($value, -\mb_strlen('"')) === '"'
        ) {
            return \mb_substr($value, 1, -1);
        }

        return $value;
    }
}


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

use Symfony\Component\VarExporter\VarExporter;
use Viserio\Component\Contract\Parser\Dumper as DumperContract;

class PhpArrayDumper implements DumperContract
{
    /**
     * {@inheritdoc}
     */
    public function dump(array $data): string
    {
        return '<?php' . \PHP_EOL . 'declare(strict_types=1);' . \PHP_EOL . \PHP_EOL . 'return ' . VarExporter::export($data) . ';' . \PHP_EOL;
    }
}


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

use Viserio\Component\Contract\Cookie\Cookie as CookieContract;
use Viserio\Component\Cookie\Traits\CookieValidatorTrait;

final class SetCookie extends AbstractCookie
{
    use CookieValidatorTrait;

    /**
     * Create a new set-cookie instance.
     *
     * @param string                             $name       the name of the cookie
     * @param null|string                        $value      the value of the cookie
     * @param null|\DateTimeInterface|int|string $expiration the time the cookie expires
     * @param string                             $path       the path on the server in which the cookie will
     *                                                       be available on
     * @param null|string                        $domain     the domain that the cookie is available to
     * @param bool                               $secure     whether the cookie should only be transmitted
     *                                                       over a secure HTTPS connection from the client
     * @param bool                               $httpOnly   whether the cookie will be made accessible only.
     *                                                       through the HTTP protocol
     * @param bool|string                        $sameSite   Whether the cookie will be available for cross-site requests
     *
     * @throws \Viserio\Component\Contract\Cookie\Exception\InvalidArgumentException
     */
    public function __construct(
        string $name,
        string $value  = null,
        $expiration    = 0,
        $path          = '/',
        $domain        = null,
        bool $secure   = false,
        bool $httpOnly = false,
        $sameSite      = false
    ) {
        $this->validateName($name);
        $this->validateValue($value);

        $this->name     = $name;
        $this->value    = $value;
        $this->maxAge   = \is_int($expiration) ? $expiration : null;
        $this->expires  = $this->normalizeExpires($expiration);
        $this->domain   = $this->normalizeDomain($domain);
        $this->path     = $this->normalizePath($path);
        $this->secure   = $secure;
        $this->httpOnly = $httpOnly;
        $this->sameSite = $this->validateSameSite($sameSite);
    }

    /**
     * Returns the cookie as a string.
     *
     * @return string The cookie
     */
    public function __toString(): string
    {
        $cookieStringParts = [];

        $cookieStringParts = $this->appendFormattedNameAndValuePartIfSet($cookieStringParts);
        $cookieStringParts = $this->appendFormattedPathPartIfSet($cookieStringParts);
        $cookieStringParts = $this->appendFormattedDomainPartIfSet($cookieStringParts);
        $cookieStringParts = $this->appendFormattedMaxAgePartIfSet($cookieStringParts);
        $cookieStringParts = $this->appendFormattedSecurePartIfSet($cookieStringParts);
        $cookieStringParts = $this->appendFormattedHttpOnlyPartIfSet($cookieStringParts);
        $cookieStringParts = $this->appendFormattedSameSitePartIfSet($cookieStringParts);

        return \implode('; ', $cookieStringParts);
    }

    /**
     * {@inheritdoc}
     */
    public function withValue(string $value = null): CookieContract
    {
        $this->validateValue($value);

        $new        = clone $this;
        $new->value = $value;

        return $new;
    }
}


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

use Viserio\Component\Contract\Config\ParameterProcessor as ParameterProcessorContract;

abstract class AbstractParameterProcessor implements ParameterProcessorContract
{
    /**
     * {@inheritdoc}
     */
    public function supports(string $parameter): bool
    {
        \preg_match('/\%' . static::getReferenceKeyword() . '\:(.*)\%/', $parameter, $matches);

        return \count($matches) !== 0;
    }

    /**
     * Get the value without the reference keyword.
     *
     * @param string $parameter
     *
     * @return string
     */
    protected function parseParameter(string $parameter): string
    {
        \preg_match('/\%' . static::getReferenceKeyword() . '\:(.*)\%/', $parameter, $matches);

        if (\count($matches) !== 0) {
            return $matches[1];
        }

        return $parameter;
    }

    /**
     * Replace parameter key with given value in data string.
     *
     * @param string $data
     * @param string $parameterKey
     * @param string $newValue
     *
     * @return mixed
     */
    protected function replaceData(string $data, string $parameterKey, string $newValue)
    {
        return \str_replace('%' . static::getReferenceKeyword() . ':' . $parameterKey . '%', $newValue, $data);
    }
}


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

use PDO;
use PDOException;
use Viserio\Component\Contract\Session\Exception\DomainException;
use Viserio\Component\Contract\Session\Exception\InvalidArgumentException;

/**
 * Session handler using a PDO connection to read and write data.
 *
 * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements
 * different locking strategies to handle concurrent access to the same session.
 * Locking is necessary to prevent loss of data due to race conditions and to keep
 * the session data consistent between read() and write(). With locking, requests
 * for the same session will wait until the other one finished writing. For this
 * reason it's best practice to close a session as early as possible to improve
 * concurrency. PHPs internal files session handler also implements locking.
 *
 * Attention: Since SQLite does not support row level locks but locks the whole database,
 * it means only one session can be accessed at a time. Even different sessions would wait
 * for another to finish. So saving session in SQLite should only be considered for
 * development or prototypes.
 *
 * Session data is a binary string that can contain non-printable characters like the null byte.
 * For this reason it must be saved in a binary column in the database like BLOB in MySQL.
 * Saving it in a character column could corrupt the data. You can use createTable()
 * to initialize a correctly defined table.
 *
 * @see http://php.net/sessionhandlerinterface
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Michael Williams <michael.williams@funsational.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class PdoSessionHandler extends AbstractSessionHandler
{
    /**
     * No locking is done. This means sessions are prone to loss of data due to
     * race conditions of concurrent requests to the same session. The last session
     * write will win in this case. It might be useful when you implement your own
     * logic to deal with this like an optimistic approach.
     */
    public const LOCK_NONE = 0;

    /**
     * Creates an application-level lock on a session. The disadvantage is that the
     * lock is not enforced by the database and thus other, unaware parts of the
     * application could still concurrently modify the session. The advantage is it
     * does not require a transaction.
     * This mode is not available for SQLite and not yet implemented for oci and sqlsrv.
     */
    public const LOCK_ADVISORY = 1;

    /**
     * Issues a real row lock. Since it uses a transaction between opening and
     * closing a session, you have to be careful when you use same database connection
     * that you also use for your application logic. This mode is the default because
     * it's the only reliable solution across DBMSs.
     */
    public const LOCK_TRANSACTIONAL = 2;

    /**
     * PDO instance or null when not connected yet.
     *
     * @var null|\PDO
     */
    private $pdo;

    /**
     * DSN string or false when lazy connection disabled.
     *
     * @var null|false|string
     */
    private $dsn = false;

    /**
     * Database driver.
     *
     * @var string
     */
    private $driver;

    /**
     * Table name.
     *
     * @var string
     */
    private $table = 'sessions';

    /**
     * Column for session id.
     *
     * @var string
     */
    private $idCol = 'sess_id';

    /**
     * Column for session data.
     *
     * @var string
     */
    private $dataCol = 'sess_data';

    /**
     * Column for lifetime.
     *
     * @var string
     */
    private $lifetimeCol = 'sess_lifetime';

    /**
     * Column for timestamp.
     *
     * @var string
     */
    private $timeCol = 'sess_time';

    /**
     * Username when lazy-connect.
     *
     * @var string
     */
    private $username = '';

    /**
     * Password when lazy-connect.
     *
     * @var string
     */
    private $password = '';

    /**
     * Connection options when lazy-connect.
     *
     * @var array
     */
    private $connectionOptions = [];

    /**
     * The strategy for locking, see constants.
     *
     * @var int
     */
    private $lockMode = self::LOCK_TRANSACTIONAL;

    /**
     * It's an array to support multiple reads before closing which is manual, non-standard usage.
     *
     * @var \PDOStatement[] An array of statements to release advisory locks
     */
    private $unlockStatements = [];

    /**
     * True when the current session exists but expired according lifetime.
     *
     * @var bool
     */
    private $sessionExpired = false;

    /**
     * Whether a transaction is active.
     *
     * @var bool
     */
    private $inTransaction = false;

    /**
     * Whether gc() has been called.
     *
     * @var bool
     */
    private $gcCalled = false;

    /**
     * The number of seconds the session should be valid.
     *
     * @var int
     */
    private $lifetime;

    /**
     * You can either pass an existing database connection as PDO instance or
     * pass a DSN string that will be used to lazy-connect to the database
     * when the session is actually used.
     *
     * List of available options:
     *     array[]
     *         ['db_table']              string The name of the table [default: sessions]
     *         ['db_id_col']             string The column where to store the session id [default: sess_id]
     *         ['db_data_col']           string The column where to store the session data [default: sess_data]
     *         ['db_lifetime_col']       string The column where to store the lifetime [default: sess_lifetime]
     *         ['db_time_col']           string The column where to store the timestamp [default: sess_time]
     *         ['db_username']           string The username when lazy-connect [default: '']
     *         ['db_password']           string The password when lazy-connect [default: '']
     *         ['db_connection_options'] array  An array of driver-specific connection options [default: array()]
     *         ['lock_mode']             int    The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
     *
     * @param \PDO|string $pdoOrDsn A \PDO instance or DSN string
     * @param int         $lifetime The session lifetime in seconds
     * @param array       $options  An associative array of options
     *
     * @throws \Viserio\Component\Contract\Session\Exception\InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
     */
    public function __construct($pdoOrDsn, int $lifetime, array $options = [])
    {
        if ($pdoOrDsn instanceof PDO) {
            if (PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(PDO::ATTR_ERRMODE)) {
                throw new InvalidArgumentException(\sprintf(
                        '[%s] requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION))',
                        __CLASS__
                ));
            }

            $this->pdo    = $pdoOrDsn;
            $this->driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
        } else {
            $this->dsn = $pdoOrDsn;
        }

        $this->table             = $options['db_table']              ?? $this->table;
        $this->idCol             = $options['db_id_col']             ?? $this->idCol;
        $this->dataCol           = $options['db_data_col']           ?? $this->dataCol;
        $this->lifetimeCol       = $options['db_lifetime_col']       ?? $this->lifetimeCol;
        $this->timeCol           = $options['db_time_col']           ?? $this->timeCol;
        $this->username          = $options['db_username']           ?? $this->username;
        $this->password          = $options['db_password']           ?? $this->password;
        $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions;
        $this->lockMode          = $options['lock_mode']             ?? $this->lockMode;
        $this->lifetime          = $lifetime;
    }

    /**
     * Returns true when the current session exists but expired according to session.gc_maxlifetime.
     *
     * Can be used to distinguish between a new session and one that expired due to inactivity.
     *
     * @return bool Whether current session expired
     */
    public function isSessionExpired(): bool
    {
        return $this->sessionExpired;
    }

    /**
     * Creates the table to store sessions which can be called once for setup.
     *
     * Session ID is saved in a column of maximum length 128 because that is enough even
     * for a 512 bit configured session.hash_function like Whirlpool. Session data is
     * saved in a BLOB. One could also use a shorter inlined varbinary column
     * if one was sure the data fits into it.
     *
     * @throws \PDOException                                                 When the table already exists
     * @throws \Viserio\Component\Contract\Session\Exception\DomainException When an unsupported PDO driver is used
     */
    public function createTable(): void
    {
        // connect if we are not yet
        $this->getConnection();

        switch ($this->driver) {
            case 'mysql':
                // We use varbinary for the ID column because it prevents unwanted conversions:
                // - character set conversions between server and client
                // - trailing space removal
                // - case-insensitivity
                // - language processing like é == e
                $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARBINARY(128) NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} MEDIUMINT NOT NULL, {$this->timeCol} INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";

                break;
            case 'sqlite':
                $sql = "CREATE TABLE {$this->table} ({$this->idCol} TEXT NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)";

                break;
            case 'pgsql':
                $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR(128) NOT NULL PRIMARY KEY, {$this->dataCol} BYTEA NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)";

                break;
            case 'oci':
                $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR2(128) NOT NULL PRIMARY KEY, {$this->dataCol} BLOB NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)";

                break;
            case 'sqlsrv':
                $sql = "CREATE TABLE {$this->table} ({$this->idCol} VARCHAR(128) NOT NULL PRIMARY KEY, {$this->dataCol} VARBINARY(MAX) NOT NULL, {$this->lifetimeCol} INTEGER NOT NULL, {$this->timeCol} INTEGER NOT NULL)";

                break;

            default:
                throw new DomainException(\sprintf(
                    'Creating the session table is currently not implemented for PDO driver [%s].',
                    $this->driver
                ));
        }

        try {
            $this->pdo->exec($sql);
        } catch (PDOException $e) {
            $this->rollback();

            throw $e;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function open($savePath, $sessionName): bool
    {
        $this->sessionExpired = false;

        if ($this->pdo === null) {
            $this->connect($this->dsn ?: $savePath);
        }

        return parent::open($savePath, $sessionName);
    }

    /**
     * {@inheritdoc}
     *
     * @throws \PDOException
     */
    public function read($sessionId): string
    {
        try {
            return parent::read($sessionId);
        } catch (PDOException $e) {
            $this->rollback();

            throw $e;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function gc($maxlifetime)
    {
        // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process.
        // This way, pruning expired sessions does not block them from being started while the current session is used.
        $this->gcCalled = true;

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \PDOException
     */
    public function updateTimestamp($sessionId, $data): bool
    {
        try {
            $updateStmt = $this->pdo->prepare(
                "UPDATE {$this->table} SET {$this->lifetimeCol} = :lifetime, {$this->timeCol} = :time WHERE {$this->idCol} = :id"
            );
            $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
            $updateStmt->bindParam(':lifetime', $this->lifetime, \PDO::PARAM_INT);
            $updateStmt->bindValue(':time', \time(), \PDO::PARAM_INT);
            $updateStmt->execute();
        } catch (PDOException $e) {
            $this->rollback();

            throw $e;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->commit();

        while ($unlockStmt = \array_shift($this->unlockStatements)) {
            $unlockStmt->execute();
        }

        if ($this->gcCalled) {
            $this->gcCalled = false;

            // delete the session records that have expired
            $sql = "DELETE FROM {$this->table} WHERE {$this->lifetimeCol} < :time - {$this->timeCol}";

            $stmt = $this->pdo->prepare($sql);
            $stmt->bindValue(':time', \time(), \PDO::PARAM_INT);
            $stmt->execute();
        }

        if ($this->dsn !== false) {
            $this->pdo = null; // only close lazy-connection
        }

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \PDOException
     */
    protected function doDestroy($sessionId): bool
    {
        // delete the record associated with this id
        $sql = "DELETE FROM {$this->table} WHERE {$this->idCol} = :id";

        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->bindParam(':id', $sessionId, PDO::PARAM_STR);
            $stmt->execute();
        } catch (PDOException $e) {
            $this->rollback();

            throw $e;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    protected function doWrite($sessionId, $data): bool
    {
        try {
            // We use a single MERGE SQL query when supported by the database.
            $mergeStmt = $this->getMergeStatement($sessionId, $data, $this->lifetime);

            if ($mergeStmt !== null) {
                $mergeStmt->execute();

                return true;
            }

            $updateStmt = $this->pdo->prepare(
                "UPDATE {$this->table} SET {$this->dataCol} = :data, {$this->lifetimeCol} = :lifetime, {$this->timeCol} = :time WHERE {$this->idCol} = :id"
            );
            $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
            $updateStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
            $updateStmt->bindParam(':lifetime', $this->lifetime, \PDO::PARAM_INT);
            $updateStmt->bindValue(':time', \time(), \PDO::PARAM_INT);
            $updateStmt->execute();

            // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in
            // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior).
            // We can just catch such an error and re-execute the update. This is similar to a serializable
            // transaction with retry logic on serialization failures but without the overhead and without possible
            // false positives due to longer gap locking.
            if (! $updateStmt->rowCount()) {
                try {
                    $insertStmt = $this->pdo->prepare(
                        "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time)"
                    );
                    $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
                    $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
                    $insertStmt->bindParam(':lifetime', $this->lifetime, \PDO::PARAM_INT);
                    $insertStmt->bindValue(':time', \time(), \PDO::PARAM_INT);
                    $insertStmt->execute();
                } catch (PDOException $e) {
                    // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys
                    if (\mb_strpos($e->getCode(), '23') === 0) {
                        $updateStmt->execute();
                    } else {
                        throw $e;
                    }
                }
            }
        } catch (PDOException $e) {
            $this->rollback();

            throw $e;
        }

        return true;
    }

    /**
     * Reads the session data in respect to the different locking strategies.
     *
     * We need to make sure we do not return session data that is already considered garbage according
     * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes.
     *
     * @param string $sessionId Session ID
     *
     * @return string The session data
     */
    protected function doRead($sessionId): string
    {
        if (self::LOCK_ADVISORY === $this->lockMode) {
            $this->unlockStatements[] = $this->doAdvisoryLock($sessionId);
        }

        $selectSql  = $this->getSelectSql();
        $selectStmt = $this->pdo->prepare($selectSql);
        $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);

        do {
            $selectStmt->execute();
            $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM);

            if ($sessionRows) {
                if ($sessionRows[0][1] + $sessionRows[0][2] < \time()) {
                    $this->sessionExpired = true;

                    return '';
                }

                return \is_resource($sessionRows[0][0]) ? \stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
            }

            if (self::LOCK_TRANSACTIONAL === $this->lockMode && $this->driver !== 'sqlite') {
                // In strict mode, session fixation is not possible: new sessions always start with a unique
                // random id, so that concurrency is not possible and this code path can be skipped.
                // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
                // until other connections to the session are committed.
                try {
                    $insertStmt = $this->pdo->prepare(
                        "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time)"
                    );
                    $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
                    $insertStmt->bindValue(':data', '', \PDO::PARAM_LOB);
                    $insertStmt->bindValue(':lifetime', 0, \PDO::PARAM_INT);
                    $insertStmt->bindValue(':time', \time(), \PDO::PARAM_INT);
                    $insertStmt->execute();
                } catch (PDOException $e) {
                    // Catch duplicate key error because other connection created the session already.
                    // It would only not be the case when the other connection destroyed the session.
                    if ($e->getCode() === 23) {
                        // Retrieve finished session data written by concurrent connection by restarting the loop.
                        // We have to start a new transaction as a failed query will mark the current transaction as
                        // aborted in PostgreSQL and disallow further queries within it.
                        $this->rollback();
                        $this->beginTransaction();

                        continue;
                    }

                    throw $e;
                }
            }

            return '';
        } while (true);
    }

    /**
     * Return a PDO instance.
     *
     * @return \PDO
     */
    private function getConnection(): PDO
    {
        if ($this->pdo === null) {
            $this->connect($this->dsn);
        }

        return $this->pdo;
    }

    /**
     * Lazy-connects to the database.
     *
     * @param string $dsn DSN string
     *
     * @return void
     */
    private function connect(string $dsn): void
    {
        $this->pdo = new PDO($dsn, $this->username, $this->password, $this->connectionOptions);
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        $this->driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
    }

    /**
     * Helper method to begin a transaction.
     *
     * Since SQLite does not support row level locks, we have to acquire a reserved lock
     * on the database immediately. Because of https://bugs.php.net/42766 we have to create
     * such a transaction manually which also means we cannot use PDO::commit or
     * PDO::rollback or PDO::inTransaction for SQLite.
     *
     * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions
     * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ .
     * So we change it to READ COMMITTED.
     */
    private function beginTransaction(): void
    {
        if (! $this->inTransaction) {
            if ($this->driver === 'sqlite') {
                $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION');
            } else {
                if ($this->driver === 'mysql') {
                    $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
                }

                $this->pdo->beginTransaction();
            }

            $this->inTransaction = true;
        }
    }

    /**
     * Helper method to commit a transaction.
     *
     * @throws \PDOException
     */
    private function commit(): void
    {
        if ($this->inTransaction) {
            try {
                // commit read-write transaction which also releases the lock
                if ($this->driver === 'sqlite') {
                    $this->pdo->exec('COMMIT');
                } else {
                    $this->pdo->commit();
                }

                $this->inTransaction = false;
            } catch (PDOException $e) {
                $this->rollback();

                throw $e;
            }
        }
    }

    /**
     * Helper method to rollback a transaction.
     */
    private function rollback(): void
    {
        // We only need to rollback if we are in a transaction. Otherwise the resulting
        // error would hide the real problem why rollback was called. We might not be
        // in a transaction when not using the transactional locking behavior or when
        // two callbacks (e.g. destroy and write) are invoked that both fail.
        if ($this->inTransaction) {
            if ($this->driver === 'sqlite') {
                $this->pdo->exec('ROLLBACK');
            } else {
                $this->pdo->rollBack();
            }

            $this->inTransaction = false;
        }
    }

    /**
     * Executes an application-level lock on the database.
     *
     * @todo implement missing advisory locks
     *       - for oci using DBMS_LOCK.REQUEST
     *       - for sqlsrv using sp_getapplock with LockOwner = Session
     *
     * @param string $sessionId
     *
     * @throws \DomainException When an unsupported PDO driver is used
     *
     * @return \PDOStatement The statement that needs to be executed later to release the lock
     */
    private function doAdvisoryLock(string $sessionId): \PDOStatement
    {
        switch ($this->driver) {
            case 'mysql':
                // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced.
                $lockId = \hash('haval256,4', $sessionId);

                // should we handle the return value? 0 on timeout, null on error
                // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout
                $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)');
                $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR);
                $stmt->execute();

                $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)');
                $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR);

                return $releaseStmt;
            case 'pgsql':
                // Obtaining an exclusive session level advisory lock requires an integer key.
                // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters.
                // So we cannot just use hexdec().
                if (\PHP_INT_SIZE <= 4) {
                    $sessionInt1 = $this->convertStringToInt($sessionId);
                    $sessionInt2 = $this->convertStringToInt(\mb_substr($sessionId, 4, 4));

                    $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)');
                    $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
                    $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
                    $stmt->execute();

                    $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)');
                    $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT);
                    $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT);
                } else {
                    $sessionBigInt = $this->convertStringToInt($sessionId);

                    $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)');
                    $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
                    $stmt->execute();

                    $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)');
                    $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT);
                }

                return $releaseStmt;
            case 'sqlite':
                throw new DomainException('SQLite does not support advisory locks.');

            default:
                throw new DomainException(\sprintf('Advisory locks are currently not implemented for PDO driver [%s].', $this->driver));
        }
    }

    /**
     * Return a locking or nonlocking SQL query to read session information.
     *
     * @throws \DomainException When an unsupported PDO driver is used
     */
    private function getSelectSql(): string
    {
        if (self::LOCK_TRANSACTIONAL === $this->lockMode) {
            $this->beginTransaction();

            switch ($this->driver) {
                case 'mysql':
                case 'oci':
                case 'pgsql':
                    return "SELECT {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol} FROM {$this->table} WHERE {$this->idCol} = :id FOR UPDATE";
                case 'sqlsrv':
                    return "SELECT {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol} FROM {$this->table} WITH (UPDLOCK, ROWLOCK) WHERE {$this->idCol} = :id";
                case 'sqlite':
                    // we already locked when starting transaction
                    break;

                default:
                    throw new DomainException(\sprintf('Transactional locks are currently not implemented for PDO driver [%s].', $this->driver));
            }
        }

        return "SELECT {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol} FROM {$this->table} WHERE {$this->idCol} = :id";
    }

    /**
     * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data.
     *
     * @param string $sessionId
     * @param string $data
     * @param int    $maxlifetime
     *
     * @return null|\PDOStatement
     */
    private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement
    {
        $mergeSql = null;

        switch (true) {
            case 'mysql' === $this->driver:
                $mergeSql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time) " .
                    "ON DUPLICATE KEY UPDATE {$this->dataCol} = VALUES({$this->dataCol}), {$this->lifetimeCol} = VALUES({$this->lifetimeCol}), {$this->timeCol} = VALUES({$this->timeCol})";

                break;
            case 'oci' === $this->driver:
                // DUAL is Oracle specific dummy table
                $mergeSql = "MERGE INTO {$this->table} USING DUAL ON ({$this->idCol} = ?) " .
                    "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " .
                    "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?";

                break;
            case 'sqlsrv' === $this->driver && \version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='):
                // MERGE is only available since SQL Server 2008 and must be terminated by semicolon
                // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
                $mergeSql = "MERGE INTO {$this->table} WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ({$this->idCol} = ?) " .
                    "WHEN NOT MATCHED THEN INSERT ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (?, ?, ?, ?) " .
                    "WHEN MATCHED THEN UPDATE SET {$this->dataCol} = ?, {$this->lifetimeCol} = ?, {$this->timeCol} = ?;";

                break;
            case 'sqlite' === $this->driver:
                $mergeSql = "INSERT OR REPLACE INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time)";

                break;
            case 'pgsql' === $this->driver && \version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='):
                $mergeSql = "INSERT INTO {$this->table} ({$this->idCol}, {$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) VALUES (:id, :data, :lifetime, :time) " .
                    "ON CONFLICT ({$this->idCol}) DO UPDATE SET ({$this->dataCol}, {$this->lifetimeCol}, {$this->timeCol}) = (EXCLUDED.{$this->dataCol}, EXCLUDED.{$this->lifetimeCol}, EXCLUDED.{$this->timeCol})";

                break;
        }

        if ($mergeSql !== null) {
            $mergeStmt = $this->pdo->prepare($mergeSql);

            if ($this->driver === 'sqlsrv' || $this->driver === 'oci') {
                $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR);
                $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR);
                $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB);
                $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT);
                $mergeStmt->bindValue(5, \time(), \PDO::PARAM_INT);
                $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB);
                $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT);
                $mergeStmt->bindValue(8, \time(), \PDO::PARAM_INT);
            } else {
                $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
                $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
                $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT);
                $mergeStmt->bindValue(':time', \time(), \PDO::PARAM_INT);
            }

            return $mergeStmt;
        }
    }

    /**
     * +     * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer.
     * +     *
     * +     * Keep in mind, PHP integers are signed.
     * +     *.
     *
     * +     * @param string $string
     * +     *
     *
     * +     * @return int
     * +     */
    private function convertStringToInt(string $string): int
    {
        if (\PHP_INT_SIZE <= 4) {
            return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]);
        }

        $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]);
        $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]);

        return $int2 + ($int1 << 32);
    }
}


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

use DirectoryIterator;

abstract class AbstractLoadFiles
{
    /**
     * Bypass given files by key.
     *
     * @var array
     */
    protected static $bypassFiles = [];

    /**
     * Get all of the files for the application.
     *
     * @param string       $path
     * @param array|string $extensions
     *
     * @return array
     */
    protected static function getFiles(string $path, $extensions = 'php'): array
    {
        if (! \file_exists($path)) {
            return [];
        }

        $files = [];
        $dir   = new DirectoryIterator($path);

        foreach ($dir as $fileinfo) {
            if (! $fileinfo->isDot()) {
                $extension = \pathinfo($fileinfo->getRealPath(), \PATHINFO_EXTENSION);

                if (\in_array($extension, (array) $extensions, true)) {
                    $filePath = $fileinfo->getRealPath();
                    $key      = \basename($filePath, '.' . $extension);

                    if (\in_array($key, static::$bypassFiles, true)) {
                        continue;
                    }

                    $files[$key] = $filePath;
                }
            }
        }

        \ksort($files, \SORT_NATURAL);

        return $files;
    }
}


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

use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\WebServer\Exception\InvalidArgumentException;
use Viserio\Component\WebServer\WebServer;

final class ServerStatusCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'server:status';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'server:status
        [--pidfile= : PID file.]
        [--filter= : The value to display (one of port, host, or address).]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Outputs the status of the local web server for the given address.';

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        if ($filter = $this->option('filter')) {
            if (WebServer::isRunning($this->option('pidfile'))) {
                [$host, $port] = \explode(':', $address = WebServer::getAddress($this->option('pidfile')));

                if ($filter === 'address') {
                    $this->line($address);
                } elseif ($filter === 'host') {
                    $this->line($host);
                } elseif ($filter === 'port') {
                    $this->line($port);
                } else {
                    throw new InvalidArgumentException(\sprintf('[%s] is not a valid filter.', $filter));
                }

                return 0;
            }
        }

        if (WebServer::isRunning($this->option('pidfile'))) {
            $this->getOutput()->success(\sprintf('Web server still listening on http://%s', WebServer::getAddress($this->option('pidfile'))));

            return 0;
        }

        $this->warn('No web server is listening.');

        return 1;
    }
}


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

use Closure;
use InvalidArgumentException;
use Viserio\Component\Contract\View\Engine as EngineContract;
use Viserio\Component\Contract\View\EngineResolver as EngineResolverContract;

class EngineResolver implements EngineResolverContract
{
    /**
     * The array of engine resolvers.
     *
     * @var array
     */
    protected $resolvers = [];

    /**
     * The resolved engine instances.
     *
     * @var array
     */
    protected $resolved = [];

    /**
     * {@inheritdoc}
     */
    public function register(string $engine, Closure $resolver): EngineResolverContract
    {
        unset($this->resolved[$engine]);

        $this->resolvers[$engine] = $resolver;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve(string $engine): EngineContract
    {
        if (isset($this->resolved[$engine])) {
            return $this->resolved[$engine];
        }

        if (isset($this->resolvers[$engine])) {
            return $this->resolved[$engine] = \call_user_func($this->resolvers[$engine]);
        }

        throw new InvalidArgumentException(\sprintf('Engine [%s] not found.', $engine));
    }
}


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

use Viserio\Component\Contract\Profiler\AssetAware as AssetAwareContract;
use Viserio\Component\Contract\Profiler\AssetsRenderer as AssetsRendererContract;
use Viserio\Component\Contract\Profiler\Profiler as ProfilerContract;

class AssetsRenderer implements AssetsRendererContract
{
    /**
     * All css files.
     *
     * @var array
     */
    protected $cssFiles = [
        'css' . \DIRECTORY_SEPARATOR . 'profiler.css',
        'css' . \DIRECTORY_SEPARATOR . 'profiler-grid.css',
    ];

    /**
     * All js files.
     *
     * @var array
     */
    protected $jsFiles = [
        'js' . \DIRECTORY_SEPARATOR . 'zepto.min.js',
        'js' . \DIRECTORY_SEPARATOR . 'profiler.js',
    ];

    /**
     * List of all icons.
     *
     * @var array
     */
    protected $icons = [
        'ic_clear_white_24px.svg'               => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_clear_white_24px.svg',
        'ic_memory_white_24px.svg'              => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_memory_white_24px.svg',
        'ic_message_white_24px.svg'             => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_message_white_24px.svg',
        'ic_narrowspark_white_24px.svg'         => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_narrowspark_white_24px.svg',
        'ic_schedule_white_24px.svg'            => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_schedule_white_24px.svg',
        'ic_storage_white_24px.svg'             => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_storage_white_24px.svg',
        'ic_mail_outline_white_24px.svg'        => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_mail_outline_white_24px.svg',
        'ic_keyboard_arrow_up_white_24px.svg'   => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_keyboard_arrow_up_white_24px.svg',
        'ic_keyboard_arrow_down_white_24px.svg' => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_keyboard_arrow_down_white_24px.svg',
        'ic_repeat_white_24px.svg'              => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_repeat_white_24px.svg',
        'ic_layers_white_24px.svg'              => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_layers_white_24px.svg',
        'ic_insert_drive_file_white_24px.svg'   => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_insert_drive_file_white_24px.svg',
        'ic_library_books_white_24px.svg'       => __DIR__ . \DIRECTORY_SEPARATOR . 'Resource' . \DIRECTORY_SEPARATOR . 'icons' . \DIRECTORY_SEPARATOR . 'ic_library_books_white_24px.svg',
    ];

    /**
     * The profiler instance.
     *
     * @var \Viserio\Component\Contract\Profiler\Profiler
     */
    protected $profiler;

    /**
     * Root path to the resource.
     *
     * @var string
     */
    protected $rootPath;

    /**
     * List of ignored collectors.
     *
     * @var array
     */
    protected $ignoredCollectors = [];

    /**
     * If jQuery is used, remove zapto.
     *
     * @var bool
     */
    protected $jqueryIsUsed;

    /**
     * Create a new file javascript renderer instance.
     *
     * @param bool        $jqueryIsUsed
     * @param null|string $rootPath
     */
    public function __construct(bool $jqueryIsUsed = false, string $rootPath = null)
    {
        $this->jqueryIsUsed = $jqueryIsUsed;
        $this->rootPath     = $rootPath ?? __DIR__ . \DIRECTORY_SEPARATOR . 'Resource';
    }

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

    /**
     * {@inheritdoc}
     */
    public function setProfiler(ProfilerContract $profiler): AssetsRendererContract
    {
        $this->profiler = $profiler;

        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setIcon(string $name, string $path): AssetsRendererContract
    {
        $this->icons[$name] = \rtrim($path, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR . $name;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setIgnoredCollector(string $name): AssetsRendererContract
    {
        $this->ignoredCollectors[] = $name;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function render(): string
    {
        if (($urlGenerator = $this->profiler->getUrlGenerator()) !== null) {
            $cssRoute = $urlGenerator->generate('profiler.assets.css', [
                'v' => $this->getModifiedTime('css'),
            ]);
            $jsRoute = $urlGenerator->generate('profiler.assets.js', [
                'v' => $this->getModifiedTime('js'),
            ]);

            $html = \sprintf(
                '<link rel="stylesheet" type="text/css" property="stylesheet" href="%s">',
                \preg_replace('/\Ahttps?:/', '', $cssRoute)
            );
            $html .= \sprintf(
                '<script type="text/javascript" src="%s"></script>',
                \preg_replace('/\Ahttps?:/', '', $jsRoute)
            );

            return $html;
        }

        return $this->renderIntoHtml();
    }

    /**
     * {@inheritdoc}
     */
    public function dumpAssetsToString(string $type): string
    {
        $files   = $this->getAssets($type);
        $content = '';

        foreach ($files as $file) {
            $content .= \file_get_contents($file) . "\n";
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function getAssets(?string $type = null): array
    {
        $cssFiles = \array_map(
            function ($css) {
                return \rtrim($this->rootPath, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR . $css;
            },
            $this->cssFiles
        );

        if ($this->jqueryIsUsed) {
            $this->jsFiles = \array_diff($this->jsFiles, ['js' . \DIRECTORY_SEPARATOR . 'zepto.min.js']);
        }

        $jsFiles = \array_map(
            function ($js) {
                return \rtrim($this->rootPath, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR . $js;
            },
            $this->jsFiles
        );

        $additionalAssets = \array_filter(
            \array_map(
                function ($collector) {
                    $collector = $collector['collector'];

                    if ($collector instanceof AssetAwareContract && ! \in_array($collector->getName(), $this->ignoredCollectors, true)) {
                        return $collector->getAssets();
                    }
                },
                $this->profiler->getCollectors()
            )
        );

        foreach ($additionalAssets as $assets) {
            if (isset($assets['css'])) {
                $cssFiles = \array_merge($cssFiles, (array) $assets['css']);
            }

            if (isset($assets['js'])) {
                $jsFiles = \array_merge($jsFiles, (array) $assets['js']);
            }
        }

        return $this->filterAssetArray([$cssFiles, $jsFiles], $type);
    }

    /**
     * Render css and js into html elements.
     *
     * @return string
     */
    protected function renderIntoHtml(): string
    {
        $html = \sprintf('<style>%s</style>', $this->dumpAssetsToString('css'));
        $html .= \sprintf('<script type="text/javascript">%s</script>', $this->dumpAssetsToString('js'));

        return $html;
    }

    /**
     * Filters a tuple of (css, js) assets according to $type.
     *
     * @param array       $array
     * @param null|string $type  'css', 'js' or null for both
     *
     * @return array
     */
    protected function filterAssetArray(array $array, ?string $type = null): array
    {
        if (\is_string($type)) {
            $type = \mb_strtolower($type);

            if ($type === 'css') {
                return $array[0];
            }

            if ($type === 'js') {
                return $array[1];
            }
        }

        return $array;
    }

    /**
     * Get the last modified time of any assets.
     *
     * @param string $type 'js' or 'css'
     *
     * @return int
     */
    protected function getModifiedTime(string $type): int
    {
        $files  = $this->getAssets($type);
        $latest = 0;

        foreach ($files as $file) {
            $mtime = (int) \filemtime($file);

            if ($mtime > $latest) {
                $latest = $mtime;
            }
        }

        return $latest;
    }
}


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

use Viserio\Component\Contract\Cron\Cron as CronContract;
use Viserio\Component\Contract\Cron\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Cron\Exception\LogicException;

class CallbackCron extends Cron
{
    /**
     * The callback to call.
     *
     * @var callable|string
     */
    protected $callback;

    /**
     * The parameters to pass to the method.
     *
     * @var array
     */
    protected $parameters;

    /**
     * Create a new callback cron instance.
     *
     * @param callable|string $callback
     * @param array           $parameters
     *
     * @throws \Viserio\Component\Contract\Cron\Exception\InvalidArgumentException
     */
    public function __construct($callback, array $parameters = [])
    {
        if (! \is_string($callback) && ! \is_callable($callback)) {
            throw new InvalidArgumentException(
                'Invalid scheduled callback cron job. Must be string or callable.'
            );
        }

        parent::__construct('');

        $this->callback   = $callback;
        $this->parameters = $parameters;
    }

    /**
     * Run the given cron job.
     *
     * @return mixed
     */
    public function run()
    {
        if ($this->description) {
            $item = $this->cachePool->getItem($this->getMutexName());
            $item->set($this->getMutexName());
            $item->expiresAfter(1440);

            $this->cachePool->save($item);
        }

        $this->callBeforeCallbacks();

        try {
            $response = $this->getInvoker()->call($this->callback, $this->parameters);
        } finally {
            if ($this->description) {
                $this->cachePool->deleteItem($this->getMutexName());
            }
        }

        $this->callAfterCallbacks();

        return $response;
    }

    /**
     * Do not allow the cron job to overlap each other.
     *
     * @throws \Viserio\Component\Contract\Cron\Exception\LogicException
     *
     * @return \Viserio\Component\Contract\Cron\Cron
     */
    public function withoutOverlapping(): CronContract
    {
        if ($this->description === null) {
            throw new LogicException(
                'A scheduled cron job description is required to prevent overlapping. ' .
                "Use the 'setDescription' method before 'withoutOverlapping'."
            );
        }

        return $this->skip(function () {
            return $this->cachePool->hasItem($this->getMutexName());
        });
    }

    /**
     * Get the summary of the event for display.
     *
     * @return string
     */
    public function getSummaryForDisplay(): string
    {
        if (\is_string($this->description)) {
            return $this->description;
        }

        return \is_string($this->callback) ? $this->callback : 'Closure';
    }

    /**
     * Get the mutex name for the scheduled command.
     *
     * @return string
     */
    protected function getMutexName(): string
    {
        return 'schedule-' . \sha1($this->expression . $this->description);
    }
}


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

use Symfony\Component\VarExporter\VarExporter;
use Viserio\Component\Contract\Routing\SegmentMatcher as SegmentMatcherContract;

class RegexMatcher extends AbstractMatcher
{
    /**
     * Used regex.
     *
     * @var string
     */
    protected $regex;

    /**
     * A group of paramters keys.
     *
     * @var array
     */
    protected $parameterKeyGroupMap;

    /**
     * Create a new regex segment matcher instance.
     *
     * @param string    $regex
     * @param array|int $parameterKeyGroupMap
     */
    public function __construct(string $regex, $parameterKeyGroupMap)
    {
        if ((\strpos($regex, '/^(') !== false && \strpos($regex, ')$/') !== false) ||
            (\strpos($regex, '/^') !== false && \strpos($regex, '$/') !== false)
        ) {
            $this->regex = $regex;
        } else {
            $this->regex = '/^(' . $regex . ')$/';
        }

        $map = \is_array($parameterKeyGroupMap) ? $parameterKeyGroupMap : [$parameterKeyGroupMap => 0];

        $this->parameterKeyGroupMap = $map;
        $this->parameterKeys        = \array_keys($map);
    }

    /**
     * Returns the used regex.
     *
     * @return string
     */
    public function getRegex(): string
    {
        return $this->regex;
    }

    /**
     * Returns the parameters key group array.
     *
     * @return array
     */
    public function getParameterKeyGroupMap(): array
    {
        return $this->parameterKeyGroupMap;
    }

    /**
     * Counted parameters keys.
     *
     * @return int
     */
    public function getGroupCount(): int
    {
        return \count(\array_unique($this->parameterKeyGroupMap, \SORT_NUMERIC));
    }

    /**
     * {@inheritdoc}
     */
    public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string
    {
        return 'preg_match('
            . VarExporter::export($this->regex)
            . ', '
            . $segmentVariable
            . ', '
            . '$matches' . $uniqueKey
            . ')';
    }

    /**
     * {@inheritdoc}
     */
    public function getMatchedParameterExpressions(string $segmentVariable, int $uniqueKey = null): array
    {
        $matches = [];

        foreach ($this->parameterKeyGroupMap as $parameterKey => $group) {
            // Use $group + 1 as the first $matches element is the full text that matched,
            // we want the groups
            $matches[$parameterKey] = '$matches' . $uniqueKey . '[' . ($group + 1) . ']';
        }

        return $matches;
    }

    /**
     * {@inheritdoc}
     */
    public function mergeParameterKeys(SegmentMatcherContract $matcher): void
    {
        parent::mergeParameterKeys($matcher);

        $this->parameterKeyGroupMap += $matcher->getParameterKeyGroupMap();
    }

    /**
     * {@inheritdoc}
     */
    protected function getMatchHash(): string
    {
        return $this->regex;
    }
}


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

use InvalidArgumentException;
use Viserio\Component\Contract\Parser\Exception\ParseException;
use Viserio\Component\Contract\Parser\Parser as ParserContract;
use Viserio\Component\Parser\Utils\XmlUtils;

class XmlParser implements ParserContract
{
    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        try {
            $dom = XmlUtils::loadString($payload);
            // Work around to accept xml input
            $data = \json_decode(\json_encode((array) \simplexml_import_dom($dom)), true);
            $data = \str_replace([':{}', ':[]'], ':null', $data);
        } catch (InvalidArgumentException $exception) {
            throw new ParseException([
                'message' => $exception->getMessage(),
                'code'    => $exception->getCode(),
                'file'    => $exception->getFile(),
                'line'    => $exception->getLine(),
            ]);
        }

        return $data;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Contract\Parser\Loader as LoaderContract;
use Viserio\Component\Parser\Dumper;
use Viserio\Component\Parser\FileLoader;
use Viserio\Component\Parser\GroupParser;
use Viserio\Component\Parser\Parser;
use Viserio\Component\Parser\TaggableParser;

class ParserServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            LoaderContract::class => static function (ContainerInterface $container): FileLoader {
                return new FileLoader();
            },
            FileLoader::class => static function (ContainerInterface $container) {
                return $container->get(LoaderContract::class);
            },
            TaggableParser::class => static function (ContainerInterface $container): TaggableParser {
                return new TaggableParser();
            },
            GroupParser::class => static function (ContainerInterface $container): GroupParser {
                return new GroupParser();
            },
            Parser::class => static function (ContainerInterface $container): Parser {
                return new Parser();
            },
            'parser' => static function (ContainerInterface $container) {
                return $container->get(Parser::class);
            },
            Dumper::class => static function (ContainerInterface $container): Dumper {
                return new Dumper();
            },
        ];
    }

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


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Bridge\Monolog\Processor\DebugProcessor;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\Profiler\Profiler as ProfilerContract;
use Viserio\Component\Log\DataCollector\LoggerDataCollector;
use Viserio\Component\Log\LogManager;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

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

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

    /**
     * {@inheritdoc}
     */
    public function getExtensions(): array
    {
        return [
            LogManager::class       => [self::class, 'extendLogManager'],
            ProfilerContract::class => [self::class, 'extendProfiler'],
        ];
    }

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

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

    /**
     * Extend monolog with a processor.
     *
     * @param \Psr\Container\ContainerInterface                      $container
     * @param null|\Monolog\Logger|\Viserio\Component\Log\LogManager $logManager
     *
     * @return null|\Monolog\Logger|\Viserio\Component\Log\Logger
     */
    public static function extendLogManager(ContainerInterface $container, $logManager = null)
    {
        $options = self::resolveOptions($container->get('config'));

        if ($logManager !== null && $options['collector']['logs'] === true) {
            $logManager->pushProcessor(new DebugProcessor());
        }

        return $logManager;
    }

    /**
     * Extend viserio profiler with a data collector.
     *
     * @param \Psr\Container\ContainerInterface                  $container
     * @param null|\Viserio\Component\Contract\Profiler\Profiler $profiler
     *
     * @return null|\Viserio\Component\Contract\Profiler\Profiler
     */
    public static function extendProfiler(
        ContainerInterface $container,
        ?ProfilerContract $profiler = null
    ): ?ProfilerContract {
        if ($profiler !== null) {
            $options = self::resolveOptions($container->get('config'));

            if ($options['collector']['logs'] === true && $container->has(LogManager::class)) {
                $profiler->addCollector(new LoggerDataCollector($container->get(LogManager::class)->getDriver()));
            }
        }

        return $profiler;
    }
}