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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Viserio\Component\Contract\Profiler\TooltipAware as TooltipAwareContract;

class MemoryDataCollector extends AbstractDataCollector implements TooltipAwareContract
{
    /**
     * Create new memory data collector.
     */
    public function __construct()
    {
        $this->reset();
    }

    /**
     * {@inheritdoc}
     */
    public function collect(ServerRequestInterface $serverRequest, ResponseInterface $response): void
    {
        $this->updateMemoryUsage();
    }

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

        return [
            'icon'  => 'ic_memory_white_24px.svg',
            'label' => $memory,
            'value' => 'MB',
            'class' => $memory > 50 ? 'yellow' : '',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getTooltip(): string
    {
        $coverToMb = static function (int $number) {
            return $number / 1024 / 1024;
        };

        $limit = $this->data['memory_limit'] === -1 ? 'Unlimited' : $coverToMb($this->data['memory_limit']);

        return $this->createTooltipGroup([
            'Peak memory usage' => $coverToMb($this->data['memory']) . ' MB',
            'PHP memory limit'  => $limit . ' MB',
        ]);
    }

    /**
     * Updates the memory usage data.
     */
    public function updateMemoryUsage(): void
    {
        $this->data['memory'] = \memory_get_peak_usage(true);
    }

    /**
     * {@inheritdoc}
     */
    public function reset(): void
    {
        $memoryLimit = \ini_get('memory_limit');

        $this->data = [
            'memory'       => 0,
            'memory_limit' => $memoryLimit === '-1' ? -1 : self::convertToBytes($memoryLimit),
        ];
    }
}


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

class ExpressionMatcher extends AbstractMatcher
{
    /**
     * The expression string.
     *
     * @var string
     */
    protected $expression;

    /**
     * Create a new expression segment matcher instance.
     *
     * @param string $expression
     * @param array  $parameterKeys
     */
    public function __construct(string $expression, array $parameterKeys)
    {
        $this->expression    = $expression;
        $this->parameterKeys = $parameterKeys;
    }

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

    /**
     * {@inheritdoc}
     */
    public function getConditionExpression(string $segmentVariable, int $uniqueKey = null): string
    {
        return \str_replace('{segment}', $segmentVariable, $this->expression);
    }

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


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

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use Viserio\Component\Contract\Exception\Displayer as DisplayerContract;
use Viserio\Component\Contract\HttpFactory\Traits\ResponseFactoryAwareTrait;
use Whoops\Handler\Handler;
use Whoops\Run as Whoops;

abstract class AbstractWhoopsDisplayer implements DisplayerContract
{
    use ResponseFactoryAwareTrait;

    /**
     * Create a new whoops displayer instance.
     *
     * @param \Psr\Http\Message\ResponseFactoryInterface $responseFactory
     */
    public function __construct(ResponseFactoryInterface $responseFactory)
    {
        $this->responseFactory = $responseFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function display(Throwable $exception, string $id, int $code, array $headers): ResponseInterface
    {
        $response = $this->responseFactory->createResponse($code);

        foreach (\array_merge($headers, ['Content-Type' => $this->getContentType()]) as $header => $value) {
            $response = $response->withAddedHeader($header, $value);
        }

        $body = $response->getBody();
        $body->write($this->getWhoops()->handleException($exception));
        $body->rewind();

        return $response->withBody($body);
    }

    /**
     * {@inheritdoc}
     */
    public function canDisplay(Throwable $original, Throwable $transformed, int $code): bool
    {
        return \class_exists(Whoops::class);
    }

    /**
     * Get the Whoops handler.
     *
     * @return \Whoops\Handler\Handler
     */
    abstract protected function getHandler(): Handler;

    /**
     * Returns the whoops instance.
     *
     * @return Whoops
     */
    private function getWhoops(): Whoops
    {
        $whoops = new Whoops();
        $whoops->allowQuit(false);
        $whoops->writeToOutput(false);
        $whoops->pushHandler($this->getHandler());

        return $whoops;
    }
}


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

use Psr\Http\Message\ServerRequestInterface;
use Throwable;
use Viserio\Component\Contract\Exception\Filter as FilterContract;

class CanDisplayFilter implements FilterContract
{
    /**
     * {@inheritdoc}
     */
    public function filter(
        array $displayers,
        ServerRequestInterface $request,
        Throwable $original,
        Throwable $transformed,
        int $code
    ): array {
        foreach ($displayers as $index => $displayer) {
            if (! $displayer->canDisplay($original, $transformed, $code)) {
                unset($displayers[$index]);
            }
        }

        return \array_values($displayers);
    }
}


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

use Closure;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Spatie\Macroable\Macroable;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Routing\Dispatcher as DispatcherContract;
use Viserio\Component\Contract\Routing\PendingResourceRegistration as PendingResourceRegistrationContract;
use Viserio\Component\Contract\Routing\Route as RouteContract;
use Viserio\Component\Contract\Routing\RouteCollection as RouteCollectionContract;
use Viserio\Component\Contract\Routing\Router as RouterContract;
use Viserio\Component\Routing\Route\Collection as RouteCollection;
use Viserio\Component\Routing\Route\Group as RouteGroup;
use Viserio\Component\Support\Traits\InvokerAwareTrait;

class Router implements RouterContract
{
    use ContainerAwareTrait;
    use InvokerAwareTrait;
    use Macroable {
        __call as macroCall;
    }

    /**
     * The route collection instance.
     *
     * @var \Viserio\Component\Routing\Route\Collection
     */
    protected $routes;

    /**
     * The dispatcher instance.
     *
     * @var \Viserio\Component\Contract\Routing\Dispatcher
     */
    protected $dispatcher;

    /**
     * The globally available parameter patterns.
     *
     * @var string[]
     */
    protected $globalParameterConditions = [];

    /**
     * The route group attribute stack.
     *
     * @var array
     */
    protected $groupStack = [];

    /**
     * The globally available parameter patterns.
     *
     * @var array
     */
    protected $patterns = [];

    /**
     * Create a new Router instance.
     *
     * @param \Viserio\Component\Contract\Routing\Dispatcher $dispatcher
     */
    public function __construct(DispatcherContract $dispatcher)
    {
        $this->dispatcher = $dispatcher;
        $this->routes     = new RouteCollection();
    }

    /**
     * Dynamically handle calls into the router instance.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @throws \BadMethodCallException
     *
     * @return mixed
     */
    public function __call(string $method, array $parameters)
    {
        return $this->macroCall($method, $parameters);
    }

    /**
     * {@inheritdoc}
     */
    public function getRoutes(): RouteCollectionContract
    {
        return $this->routes;
    }

    /**
     * {@inheritdoc}
     */
    public function getDispatcher(): DispatcherContract
    {
        return $this->dispatcher;
    }

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

    /**
     * {@inheritdoc}
     */
    public function hasGroupStack(): bool
    {
        return \count($this->groupStack) !== 0;
    }

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

    /**
     * {@inheritdoc}
     */
    public function patterns(array $patterns): RouterContract
    {
        foreach ($patterns as $key => $pattern) {
            $this->pattern($key, $pattern);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $uri, $action = null): RouteContract
    {
        return $this->addRoute([self::METHOD_GET, self::METHOD_HEAD], $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function post(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(self::METHOD_POST, $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function put(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(self::METHOD_PUT, $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function patch(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(self::METHOD_PATCH, $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function head(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(self::METHOD_HEAD, $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function delete(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(self::METHOD_DELETE, $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function options(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(self::METHOD_OPTIONS, $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function any(string $uri, $action = null): RouteContract
    {
        return $this->addRoute(
            [
                self::METHOD_HEAD,
                self::METHOD_GET,
                self::METHOD_POST,
                self::METHOD_PUT,
                self::METHOD_PATCH,
                self::METHOD_DELETE,
                self::METHOD_PURGE,
                self::METHOD_OPTIONS,
                self::METHOD_TRACE,
                self::METHOD_CONNECT,
                self::METHOD_TRACE,
                self::METHOD_LINK,
                self::METHOD_UNLINK,
            ],
            $uri,
            $action
        );
    }

    /**
     * {@inheritdoc}
     */
    public function match($methods, string $uri, $action = null): RouteContract
    {
        return $this->addRoute(\array_map('strtoupper', (array) $methods), $uri, $action);
    }

    /**
     * {@inheritdoc}
     */
    public function pattern(string $key, string $pattern): RouterContract
    {
        $this->patterns[$key] = $pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function addParameter(string $parameterName, string $expression): RouterContract
    {
        $this->globalParameterConditions[$parameterName] = $expression;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function removeParameter(string $name): void
    {
        unset($this->globalParameterConditions[$name]);
    }

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

    /**
     * {@inheritdoc}
     */
    public function resources(array $resources): void
    {
        foreach ($resources as $name => $controller) {
            $this->resource($name, $controller);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function resource(string $name, string $controller, array $options = []): PendingResourceRegistrationContract
    {
        return new PendingResourceRegistration(
            new ResourceRegistrar($this),
            $name,
            $controller,
            $options
        );
    }

    /**
     * {@inheritdoc}
     */
    public function group(array $attributes, $routes): void
    {
        $this->updateGroupStack($attributes);

        $router = $this;

        if ($routes instanceof Closure) {
            $routes($router);
        } else {
            require $routes;
        }

        \array_pop($this->groupStack);
    }

    /**
     * {@inheritdoc}
     */
    public function mergeWithLastGroup(array $new): array
    {
        return RouteGroup::merge($new, \end($this->groupStack));
    }

    /**
     * {@inheritdoc}
     */
    public function getLastGroupSuffix(): string
    {
        if ($this->hasGroupStack()) {
            $last = \end($this->groupStack);

            return $last['suffix'] ?? '';
        }

        return '';
    }

    /**
     * {@inheritdoc}
     */
    public function getLastGroupPrefix(): string
    {
        if ($this->hasGroupStack()) {
            $last = \end($this->groupStack);

            return $last['prefix'] ?? '';
        }

        return '';
    }

    /**
     * {@inheritdoc}
     */
    public function getCurrentRoute(): ?RouteContract
    {
        return $this->dispatcher->getCurrentRoute();
    }

    /**
     * {@inheritdoc}
     */
    public function dispatch(ServerRequestInterface $request): ResponseInterface
    {
        $dispatcher = $this->dispatcher;

        if ($this->container !== null && \method_exists($dispatcher, 'setContainer')) {
            $dispatcher->setContainer($this->container);
        }

        return $dispatcher->handle($this->routes, $request);
    }

    /**
     * Add a route to the underlying route collection.
     *
     * @param array|string               $methods
     * @param string                     $uri
     * @param null|array|\Closure|string $action
     *
     * @return \Viserio\Component\Contract\Routing\Route
     */
    protected function addRoute($methods, string $uri, $action): RouteContract
    {
        return $this->routes->add($this->createRoute($methods, $uri, $action));
    }

    /**
     * Create a new route instance.
     *
     * @param array|string $methods
     * @param string       $uri
     * @param mixed        $action
     *
     * @return \Viserio\Component\Contract\Routing\Route
     */
    protected function createRoute($methods, string $uri, $action): RouteContract
    {
        // If the route is routing to a controller we will parse the route action into
        // an acceptable array format before registering it and creating this route
        // instance itself. We need to build the Closure that will call this out.
        if ($this->actionReferencesController($action)) {
            $action = $this->convertToControllerAction($action);
        }

        $route = new Route($methods, $this->prefix($this->suffix($uri)), $action);

        if ($this->container !== null) {
            $route->setContainer($this->container);
        }

        $route->setInvoker($this->getInvoker());

        if ($this->hasGroupStack()) {
            $this->mergeGroupAttributesIntoRoute($route);
        }

        $this->addWhereClausesToRoute($route);

        foreach ($this->globalParameterConditions as $key => $value) {
            $route->addParameter($key, $value);
        }

        return $route;
    }

    /**
     * Add the necessary where clauses to the route based on its initial registration.
     *
     * @param \Viserio\Component\Contract\Routing\Route $route
     *
     * @return void
     */
    protected function addWhereClausesToRoute(RouteContract $route): void
    {
        $where   = $route->getAction()['where'] ?? [];
        $pattern = \array_merge($this->patterns, $where);

        foreach ($pattern as $name => $value) {
            $route->where($name, $value);
        }
    }

    /**
     * Merge the group stack with the controller action.
     *
     * @param \Viserio\Component\Contract\Routing\Route $route
     *
     * @return void
     */
    protected function mergeGroupAttributesIntoRoute(RouteContract $route): void
    {
        $action = $this->mergeWithLastGroup($route->getAction());

        $route->setAction($action);
    }

    /**
     * Determine if the action is routing to a controller.
     *
     * @param array|\Closure|string $action
     *
     * @return bool
     */
    protected function actionReferencesController($action): bool
    {
        if ($action instanceof Closure) {
            return false;
        }

        return \is_string($action) || (isset($action['uses']) && \is_string($action['uses']));
    }

    /**
     * Add a controller based route action to the action array.
     *
     * @param array|string $action
     *
     * @return array
     */
    protected function convertToControllerAction($action): array
    {
        if (\is_string($action)) {
            $action = ['uses' => $action];
        }

        if ($this->hasGroupStack()) {
            $action['uses'] = $this->prependGroupNamespace($action['uses']);
        }

        $action['controller'] = $action['uses'];

        return $action;
    }

    /**
     * Prepend the last group uses onto the use clause.
     *
     * @param string $uses
     *
     * @return string
     */
    protected function prependGroupNamespace(string $uses): string
    {
        $group = \end($this->groupStack);

        return isset($group['namespace']) && \strpos($uses, '\\') !== 0 ?
            $group['namespace'] . '\\' . $uses :
            $uses;
    }

    /**
     * Prefix the given URI with the last prefix.
     *
     * @param string $uri
     *
     * @return string
     */
    protected function prefix(string $uri): string
    {
        $trimmed = \trim($this->getLastGroupPrefix(), '/') . '/' . \trim($uri, '/');

        if (! $trimmed) {
            return '/';
        }

        if (\strpos($trimmed, '/') === 0) {
            return $trimmed;
        }

        return '/' . $trimmed;
    }

    /**
     * Suffix the given URI with the last suffix.
     *
     * @param string $uri
     *
     * @return string
     */
    protected function suffix(string $uri): string
    {
        return \trim($uri) . \trim($this->getLastGroupSuffix());
    }

    /**
     * Update the group stack with the given attributes.
     *
     * @param array $attributes
     *
     * @return void
     */
    protected function updateGroupStack(array $attributes): void
    {
        if ($this->hasGroupStack()) {
            $attributes = RouteGroup::merge($attributes, \end($this->groupStack));
        }

        $this->groupStack[] = $attributes;
    }
}


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

use Throwable;
use Viserio\Component\Contract\Parser\Exception\ParseException;
use Viserio\Component\Contract\Parser\Parser as ParserContract;

class PhpArrayParser implements ParserContract
{
    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        if (! \file_exists($payload)) {
            throw new ParseException(['message' => \sprintf('No such file [%s] found.', $payload)]);
        }

        try {
            $data = require $payload;
        } catch (Throwable $exception) {
            throw new ParseException(
                [
                    'message'   => \sprintf('An exception was thrown by file [%s].', $payload),
                    'exception' => $exception,
                ]
            );
        }

        return (array) $data;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Contract\Foundation\Kernel as KernelContract;
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\Foundation\DataCollector\FilesLoadedCollector;
use Viserio\Component\Foundation\DataCollector\NarrowsparkDataCollector;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

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

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

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

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

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

    /**
     * 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) {
            $options = self::resolveOptions($container->get('config'));
            $kernel  = $container->get(KernelContract::class);

            if ($options['collector']['narrowspark']) {
                $profiler->addCollector(new NarrowsparkDataCollector($kernel->getEnvironment(), $kernel->isDebug()), -100);
            }

            if ($options['collector']['files']) {
                $profiler->addCollector(new FilesLoadedCollector($kernel->getRootDir()));
            }
        }

        return $profiler;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Validation\Sanitizer;

class SanitizerServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            Sanitizer::class => [self::class, 'createSanitizer'],
            'sanitizer'      => static function (ContainerInterface $container) {
                return $container->get(Sanitizer::class);
            },
        ];
    }

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

    /**
     * Create a sanitizer instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Validation\Sanitizer
     */
    public static function createSanitizer(ContainerInterface $container): Sanitizer
    {
        $sanitizer = new Sanitizer();
        $sanitizer->setContainer($container);

        return $sanitizer;
    }
}


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

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

final class BootstrapManager
{
    /**
     * Indicates if the application has been bootstrapped before.
     *
     * @var bool
     */
    private $hasBeenBootstrapped = false;

    /**
     * The array of bootstrapping callbacks.
     *
     * @var array
     */
    private $bootstrappingCallbacks = [];

    /**
     * The array of booted callbacks.
     *
     * @var array
     */
    private $bootstrappedCallbacks = [];

    /**
     * A Kernel implementation.
     *
     * @var \Viserio\Component\Contract\Foundation\Kernel
     */
    private $kernel;

    /**
     * Create a new bootstrap manger instance.
     *
     * @param \Viserio\Component\Contract\Foundation\Kernel $kernel
     */
    public function __construct(KernelContract $kernel)
    {
        $this->kernel = $kernel;
    }

    /**
     * Determine if the application has been bootstrapped before.
     *
     * @return bool
     */
    public function hasBeenBootstrapped(): bool
    {
        return $this->hasBeenBootstrapped;
    }

    /**
     * Register a callback to run before a bootstrapper.
     *
     * @param string   $bootstrapper
     * @param callable $callback
     *
     * @return void
     */
    public function addBeforeBootstrapping(string $bootstrapper, callable $callback): void
    {
        $key = 'bootstrapping: ' . \str_replace('\\', '', $bootstrapper);

        $this->bootstrappingCallbacks[$key][] = $callback;
    }

    /**
     * Register a callback to run after a bootstrapper.
     *
     * @param string   $bootstrapper
     * @param callable $callback
     *
     * @return void
     */
    public function addAfterBootstrapping(string $bootstrapper, callable $callback): void
    {
        $key = 'bootstrapped: ' . \str_replace('\\', '', $bootstrapper);

        $this->bootstrappedCallbacks[$key][] = $callback;
    }

    /**
     * Run the given array of bootstrap classes.
     *
     * @param array $bootstraps
     *
     * @return void
     */
    public function bootstrapWith(array $bootstraps): void
    {
        foreach ($bootstraps as $bootstrap) {
            $this->callCallbacks(
                $this->bootstrappingCallbacks,
                $this->kernel,
                'bootstrapping: ',
                $bootstrap
            );

            $bootstrap::bootstrap($this->kernel);

            $this->callCallbacks(
                $this->bootstrappedCallbacks,
                $this->kernel,
                'bootstrapped: ',
                $bootstrap
            );
        }

        $this->hasBeenBootstrapped = true;
    }

    /**
     * Calls callbacks on bootstrap name.
     *
     * @param array                                         $bootCallbacks
     * @param \Viserio\Component\Contract\Foundation\Kernel $kernel
     * @param string                                        $type
     * @param string                                        $bootstrap
     *
     * @return void
     */
    private function callCallbacks(array $bootCallbacks, KernelContract $kernel, string $type, string $bootstrap): void
    {
        foreach ($bootCallbacks as $name => $callbacks) {
            if ($type . \str_replace('\\', '', $bootstrap) === $name) {
                /** @var callable $callback */
                foreach ($callbacks as $callback) {
                    $callback($kernel);
                }
            }
        }
    }
}


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

use Cake\Chronos\Chronos;
use Psr\Http\Message\ServerRequestInterface;
use SessionHandlerInterface as SessionHandlerContract;
use Viserio\Component\Contract\Session\Exception\InvalidArgumentException;
use Viserio\Component\Contract\Session\Exception\SessionNotStartedException;
use Viserio\Component\Contract\Session\Exception\SuspiciousOperationException;
use Viserio\Component\Contract\Session\Fingerprint as FingerprintContract;
use Viserio\Component\Contract\Session\Store as StoreContract;
use Viserio\Component\Session\Handler\CookieSessionHandler;
use Viserio\Component\Support\Str;

class Store implements StoreContract
{
    /**
     * The session ID.
     *
     * @var null|string
     */
    protected $id;

    /**
     * The session name.
     *
     * @var string
     */
    protected $name;

    /**
     * Session store started status.
     *
     * @var bool
     */
    protected $started = false;

    /**
     * The session handler implementation.
     *
     * @var SessionHandlerContract
     */
    protected $handler;

    /**
     * Number of requests after which id is regenerated.
     *
     * @var null|int
     */
    private $idRequestsLimit;

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

    /**
     * Last (id) regeneration (Unix timestamp).
     *
     * @var null|int
     */
    private $regenerationTrace;

    /**
     * First trace (Unix timestamp), time when session was created.
     *
     * @var null|int
     */
    private $firstTrace;

    /**
     * Last trace (Unix timestamp).
     *
     * @var null|int
     */
    private $lastTrace;

    /**
     * Counted requests.
     *
     * @var int
     */
    private $requestsCount = 0;

    /**
     * All fingerprint generators.
     *
     * @var array
     */
    private $fingerprintGenerators = [];

    /**
     * Full fingerprint.
     *
     * @var string
     */
    private $fingerprint = '';

    /**
     * The session values.
     *
     * @var array
     */
    private $values = [];

    /**
     * Create a new session instance.
     *
     * @param string                   $name
     * @param \SessionHandlerInterface $handler
     */
    public function __construct(string $name, SessionHandlerContract $handler)
    {
        $this->setName($name);
        $this->handler = $handler;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setId(string $id): void
    {
        if (! $this->isValidId($id)) {
            $id = $this->generateSessionId();
        }

        $this->id = $id;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setName(string $name): void
    {
        \parse_str($name, $parsed);

        if (\implode('&', \array_keys($parsed)) !== $name) {
            throw new InvalidArgumentException(\sprintf('Session name [%s] contains illegal character(s).', $name));
        }

        $this->name = $name;
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function setIdRequestsLimit(int $limit): void
    {
        $this->idRequestsLimit = $limit;
    }

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

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

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

    /**
     * {@inheritdoc}
     */
    public function getRequestsCount(): int
    {
        return $this->requestsCount;
    }

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

    /**
     * {@inheritdoc}
     */
    public function start(): bool
    {
        $this->started = true;

        $this->id     = $this->generateSessionId();
        $this->values = [];

        $this->firstTrace = $this->getTimestamp();
        $this->updateLastTrace();

        $this->requestsCount     = 1;
        $this->regenerationTrace = $this->getTimestamp();

        $this->fingerprint = $this->generateFingerprint();

        if (! $this->has('_token')) {
            $this->regenerateToken();
        }

        return $this->started;
    }

    /**
     * {@inheritdoc}
     */
    public function open(): bool
    {
        if (! $this->started) {
            if ($this->getId() !== null) {
                $this->loadSession();

                if ($this->getFirstTrace() === null) {
                    return false;
                }

                if ($this->generateFingerprint() !== $this->getFingerprint()) {
                    throw new SuspiciousOperationException();
                }

                $this->started = true;
                $this->requestsCount++;
            }
        }

        return $this->started;
    }

    /**
     * {@inheritdoc}
     */
    public function isExpired(): bool
    {
        $lastTrace = $this->getLastTrace();

        if ($lastTrace === null) {
            return true;
        }

        return $lastTrace + $this->getTtl() < \time();
    }

    /**
     * {@inheritdoc}
     */
    public function invalidate(): bool
    {
        $this->clear();

        return $this->migrate(true);
    }

    /**
     * {@inheritdoc}
     */
    public function migrate(bool $destroy = false): bool
    {
        if ($destroy) {
            $this->handler->destroy($this->id);
        }

        $this->id                = $this->generateSessionId();
        $this->regenerationTrace = $this->getTimestamp();
        $this->requestsCount     = 0;

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function save(): void
    {
        if (! $this->started) {
            return;
        }

        if ($this->shouldRegenerateId()) {
            $this->migrate(true);
        }

        $this->updateLastTrace();
        $this->ageFlashData();
        $this->writeToHandler();

        $this->values  = [];
        $this->started = false;
    }

    /**
     * {@inheritdoc}
     */
    public function has(string $name): bool
    {
        $this->checkIfSessionHasStarted();

        return isset($this->values[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function get(string $name, $default = null)
    {
        $this->checkIfSessionHasStarted();

        return $this->has($name) ? $this->values[$name] : $default;
    }

    /**
     * {@inheritdoc}
     */
    public function set(string $name, $value): void
    {
        $this->checkIfSessionHasStarted();

        $this->values[$name] = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function push(string $key, $value): void
    {
        $array = $this->get($key, []);

        $array[] = $value;

        $this->set($key, $array);
    }

    /**
     * {@inheritdoc}
     */
    public function remove(string $name)
    {
        $value = $this->get($name);

        if ($this->has($name)) {
            unset($this->values[$name]);
        }

        return $value;
    }

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

    /**
     * {@inheritdoc}
     */
    public function clear(): void
    {
        $this->values = [];
    }

    /**
     * {@inheritdoc}
     */
    public function setIdLiveTime(int $ttl): void
    {
        $this->idTtl = $ttl;
    }

    /**
     * {@inheritdoc}
     */
    public function getTtl(): int
    {
        return $this->idTtl;
    }

    /**
     * {@inheritdoc}
     */
    public function ageFlashData(): void
    {
        foreach ($this->get('_flash.old', []) as $old) {
            $this->remove($old);
        }

        $this->set('_flash.old', $this->get('_flash.new', []));

        $this->set('_flash.new', []);
    }

    /**
     * {@inheritdoc}
     */
    public function flash(string $key, $value): void
    {
        $this->set($key, $value);

        $this->push('_flash.new', $key);

        $this->removeFromOldFlashData([$key]);
    }

    /**
     * {@inheritdoc}
     */
    public function now(string $key, $value): void
    {
        $this->set($key, $value);

        $this->push('_flash.old', $key);
    }

    /**
     * {@inheritdoc}
     */
    public function reflash(): void
    {
        $this->mergeNewFlashes($this->get('_flash.old', []));

        $this->set('_flash.old', []);
    }

    /**
     * {@inheritdoc}
     */
    public function keep($keys = null): void
    {
        $keys = \is_array($keys) ? $keys : \func_get_args();

        $this->mergeNewFlashes($keys);

        $this->removeFromOldFlashData($keys);
    }

    /**
     * {@inheritdoc}
     */
    public function addFingerprintGenerator(FingerprintContract $fingerprintGenerator): void
    {
        $this->fingerprintGenerators[] = $fingerprintGenerator;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setRequestOnHandler(ServerRequestInterface $request): void
    {
        if ($this->handlerNeedsRequest()) {
            $this->handler->setRequest($request);
        }
    }

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

    /**
     * Get the CSRF token value.
     *
     * @return string
     */
    public function getToken(): string
    {
        $token = $this->get('_token');

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

        return '';
    }

    /**
     * Regenerate the CSRF token value.
     *
     * @return void
     */
    public function regenerateToken(): void
    {
        $this->set('_token', \bin2hex(Str::random(40)));
    }

    /**
     * {@inheritdoc}
     */
    public function getPreviousUrl(): ?string
    {
        return $this->get('_previous.url');
    }

    /**
     * {@inheritdoc}
     */
    public function setPreviousUrl(string $url): void
    {
        $this->set('_previous.url', $url);
    }

    /**
     * Determine if this is a valid session ID.
     *
     * @param string $id
     *
     * @return bool
     */
    protected function isValidId($id): bool
    {
        return \is_string($id) && \preg_match('/^[a-f0-9]{40}$/', $id) === 1;
    }

    /**
     * Updates last trace timestamp.
     *
     * @return void
     */
    protected function updateLastTrace(): void
    {
        $this->lastTrace = $this->getTimestamp();
    }

    /**
     * Get a new, random session ID.
     *
     * @return string
     */
    protected function generateSessionId(): string
    {
        return \hash('ripemd160', \uniqid(Str::random(23), true) . Str::random(25) . \microtime(true));
    }

    /**
     * Check if session has already started.
     *
     * @throws \Viserio\Component\Contract\Session\Exception\SessionNotStartedException
     *
     * @return void
     */
    protected function checkIfSessionHasStarted(): void
    {
        if (! $this->isStarted()) {
            throw new SessionNotStartedException('The session is not started.');
        }
    }

    /**
     * Prepare the raw string data from the session.
     *
     * @param string $data
     *
     * @return array
     */
    protected function prepareForReadFromHandler($data): array
    {
        $value = \json_decode($data, true);

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

        return $value;
    }

    /**
     * Prepare the session data for storage.
     *
     * @param string $data
     *
     * @return string
     */
    protected function prepareForWriteToHandler(string $data): string
    {
        return $data;
    }

    /**
     * Merge new flash keys into the new flash array.
     *
     * @param array $keys
     *
     * @return void
     */
    private function mergeNewFlashes(array $keys): void
    {
        $values = \array_unique(\array_merge($this->get('_flash.new', []), $keys));

        $this->set('_flash.new', $values);
    }

    /**
     * Remove the given keys from the old flash data.
     *
     * @param array $keys
     *
     * @return void
     */
    private function removeFromOldFlashData(array $keys): void
    {
        $this->set('_flash.old', \array_diff($this->get('_flash.old', []), $keys));
    }

    /**
     * Determine if session id should be regenerated? (based on requestsCount or regenerationTrace).
     *
     * @return bool
     */
    private function shouldRegenerateId(): bool
    {
        if ($this->idRequestsLimit !== null && ($this->requestsCount >= $this->idRequestsLimit)) {
            return true;
        }

        if ($this->regenerationTrace !== 0) {
            $expires = Chronos::createFromTimestamp($this->regenerationTrace)->addSeconds($this->getTtl())->getTimestamp();

            return $expires < $this->getTimestamp();
        }

        return false;
    }

    /**
     * Load the session data from the handler.
     *
     * @return bool
     */
    private function loadSession(): bool
    {
        $values = $this->readFromHandler();

        if (\count($values) === 0) {
            return false;
        }

        $metadata = $values[self::METADATA_NAMESPACE];

        $this->firstTrace        = $metadata['firstTrace'];
        $this->lastTrace         = $metadata['lastTrace'];
        $this->regenerationTrace = $metadata['regenerationTrace'];
        $this->requestsCount     = $metadata['requestsCount'];
        $this->fingerprint       = $metadata['fingerprint'];

        $this->values = \array_merge($this->values, $values);

        return true;
    }

    /**
     * Read the session data from the handler.
     *
     * @return array
     */
    private function readFromHandler(): array
    {
        $data = $this->handler->read($this->id);

        if ($data === '') {
            return [];
        }

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

    /**
     * Write values to handler.
     *
     * @return void
     */
    private function writeToHandler(): void
    {
        $values = $this->values;

        $values[self::METADATA_NAMESPACE] = [
            'firstTrace'        => $this->firstTrace,
            'lastTrace'         => $this->lastTrace,
            'regenerationTrace' => $this->regenerationTrace,
            'requestsCount'     => $this->requestsCount,
            'fingerprint'       => $this->fingerprint,
        ];

        $value = \json_encode($values, \JSON_PRESERVE_ZERO_FRACTION);

        $this->handler->write(
            $this->id,
            $this->prepareForWriteToHandler($value)
        );
    }

    /**
     * Generate a fingerprint string.
     *
     * @return string
     */
    private function generateFingerprint(): string
    {
        $fingerprint = '';

        foreach ($this->fingerprintGenerators as $fingerprintGenerator) {
            $fingerprint .= $fingerprintGenerator->generate();
        }

        return $fingerprint;
    }

    /**
     * Get time stamp.
     *
     * @return int
     */
    private function getTimestamp(): int
    {
        return Chronos::now()->toMutable()->getTimestamp();
    }
}


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

use Viserio\Component\Config\ParameterProcessor\ComposerExtraProcessor;
use Viserio\Component\Contract\Config\Repository as RepositoryContract;
use Viserio\Component\Contract\Foundation\BootstrapState as BootstrapStateContract;
use Viserio\Component\Contract\Foundation\Kernel as KernelContract;
use Viserio\Component\Foundation\Config\Processor\DirectoryProcessor;

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

    /**
     * {@inheritdoc}
     */
    public static function getType(): string
    {
        return BootstrapStateContract::TYPE_BEFORE;
    }

    /**
     * {@inheritdoc}
     */
    public static function getBootstrapper(): string
    {
        return LoadServiceProvider::class;
    }

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

        $config  = $container->get(RepositoryContract::class);
        $rootDir = \rtrim($kernel->getRootDir(), \DIRECTORY_SEPARATOR);

        $config->addParameterProcessor(new ComposerExtraProcessor($rootDir . \DIRECTORY_SEPARATOR . 'composer.json'));
        $config->addParameterProcessor(new DirectoryProcessor($config, $container));
    }
}


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

class EnvParameterProcessor extends AbstractParameterProcessor
{
    /**
     * {@inheritdoc}
     */
    public static function getReferenceKeyword(): string
    {
        return 'env';
    }

    /**
     * {@inheritdoc}
     */
    public function process(string $data)
    {
        $parameterKey = $this->parseParameter($data);

        $value = \getenv($parameterKey);

        if ($value === false) {
            return $parameterKey;
        }

        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 $this->replaceData($data, $parameterKey, '');
        }

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

        return $this->replaceData($data, $parameterKey, $value);
    }
}


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

use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use Narrowspark\Collection\Collection;
use Throwable;
use Viserio\Component\Contract\Pagination\Paginator as PaginatorContract;
use Viserio\Component\Contract\Support\Arrayable as ArrayableContract;
use Viserio\Component\Contract\Support\Jsonable as JsonableContract;
use Viserio\Component\Contract\Support\Stringable as StringableContract;

abstract class AbstractPaginator implements
    ArrayAccess,
    Countable,
    IteratorAggregate,
    StringableContract,
    ArrayableContract,
    JsonSerializable,
    JsonableContract,
    PaginatorContract
{
    /**
     * All of the items being paginated.
     *
     * @var \Narrowspark\Collection\Collection
     */
    protected $items;

    /**
     * The number of items to be shown per page.
     *
     * @var int
     */
    protected $itemCountPerPage;

    /**
     * The current page being "viewed".
     *
     * @var int
     */
    protected $currentPage;

    /**
     * The base path to assign to all URLs.
     *
     * @var string
     */
    protected $path = '/';

    /**
     * The query parameters to add to all URLs.
     *
     * @var array
     */
    protected $query = [];

    /**
     * The URL fragment to add to all URLs.
     *
     * @var null|string
     */
    protected $fragment;

    /**
     * The query string variable used to store the page.
     *
     * @var string
     */
    protected $pageName = 'page';

    /**
     * The current page resolver callback.
     *
     * @var \Closure
     */
    protected $currentPageResolver;

    /**
     * The current request instance.
     *
     * @var \Psr\Http\Message\ServerRequestInterface
     */
    protected $request;

    /**
     * Make dynamic calls into the collection.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @return mixed
     *
     * @codeCoverageIgnore
     */
    public function __call(string $method, array $parameters)
    {
        return $this->items->{$method}(...$parameters);
    }

    /**
     * {@inheritdoc}
     */
    public function __toString(): string
    {
        try {
            return $this->render();
        } catch (Throwable $exception) {
            // Really, PHP? https://bugs.php.net/bug.php?id=53648
            \trigger_error(self::class . '::__toString exception: ' . (string) $exception, \E_USER_ERROR);

            return '';
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getItems(): array
    {
        return $this->items->all();
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function getCurrentPage(): int
    {
        return $this->currentPage;
    }

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

    /**
     * {@inheritdoc}
     */
    public function setPath(string $path): PaginatorContract
    {
        $this->path = $path !== '/' ? \rtrim($path, '/') : $path;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function getFragment(): ?string
    {
        return $this->fragment;
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function setFragment(string $fragment): PaginatorContract
    {
        $this->fragment = $fragment;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function getPageName(): string
    {
        return $this->pageName;
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function setPageName(string $name): PaginatorContract
    {
        $this->pageName = $name;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getUrlRange(int $start, int $end): array
    {
        $urls = [];

        for ($page = $start; $page <= $end; $page++) {
            $urls[$page] = $this->getUrl($page);
        }

        return $urls;
    }

    /**
     * {@inheritdoc}
     */
    public function getUrl(int $page): string
    {
        if ($page <= 0) {
            $page = 1;
        }

        // Extra information like sortings storage.
        $parameters = [$this->pageName => $page];

        if (\count($this->query) > 0) {
            $parameters = \array_merge($this->query, $parameters);
        }

        $url = $this->path;
        $url .= (\mb_strpos($this->path, '?') !== false ? '&' : '?');
        $url .= \http_build_query($parameters, '', '&');
        $url .= $this->buildFragment();

        return $url;
    }

    /**
     * {@inheritdoc}
     */
    public function getPreviousPageUrl(): ?string
    {
        if ($this->getCurrentPage() > 1) {
            return $this->getUrl($this->getCurrentPage() - 1);
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function addQuery(string $key, string $value): PaginatorContract
    {
        if ($this->pageName !== $key) {
            $this->query[$key] = $value;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function appends($key, string $value = null): PaginatorContract
    {
        if (\is_array($key)) {
            return $this->appendArray($key);
        }

        return $this->addQuery($key, $value);
    }

    /**
     * Get an iterator for the items.
     *
     * @return \ArrayIterator
     *
     * @codeCoverageIgnore
     */
    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->items->all());
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function isEmpty(): bool
    {
        return $this->items->isEmpty();
    }

    /**
     * Get the number of items for the current page.
     *
     * @return int
     */
    public function count(): int
    {
        return $this->items->count();
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstItem(): int
    {
        if (\count($this->items) === 0) {
            return 0;
        }

        return ($this->currentPage - 1) * $this->itemCountPerPage + 1;
    }

    /**
     * {@inheritdoc}
     */
    public function getLastItem(): int
    {
        if (\count($this->items) === 0) {
            return 0;
        }

        return $this->getFirstItem() + $this->count() - 1;
    }

    /**
     * {@inheritdoc}
     */
    public function onFirstPage(): bool
    {
        return $this->getCurrentPage() <= 1;
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function getItemsPerPage(): int
    {
        return $this->itemCountPerPage;
    }

    /**
     * {@inheritdoc}
     */
    public function hasPages(): bool
    {
        return ! ($this->getCurrentPage() === 1 && ! $this->hasMorePages());
    }

    /**
     * Set the paginator's underlying collection.
     *
     * @param \Narrowspark\Collection\Collection $collection
     *
     * @return $this
     *
     * @codeCoverageIgnore
     */
    public function setCollection(Collection $collection)
    {
        $this->items = $collection;

        return $this;
    }

    /**
     * Get the paginator's underlying collection.
     *
     * @return \Narrowspark\Collection\Collection
     *
     * @codeCoverageIgnore
     */
    public function getCollection(): Collection
    {
        return $this->items;
    }

    /**
     * Determine if the given item exists.
     *
     * @param mixed $key
     *
     * @return bool
     *
     * @codeCoverageIgnore
     */
    public function offsetExists($key): bool
    {
        return $this->items->has($key);
    }

    /**
     * Get the item at the given offset.
     *
     * @param mixed $key
     *
     * @return mixed
     *
     * @codeCoverageIgnore
     */
    public function offsetGet($key)
    {
        return $this->items->get($key);
    }

    /**
     * Set the item at the given offset.
     *
     * @param mixed $key
     * @param mixed $value
     *
     * @codeCoverageIgnore
     */
    public function offsetSet($key, $value): void
    {
        $this->items->put($key, $value);
    }

    /**
     * Unset the item at the given key.
     *
     * @param mixed $key
     *
     * @codeCoverageIgnore
     */
    public function offsetUnset($key): void
    {
        $this->items->forget($key);
    }

    /**
     * Build the full fragment portion of a URL.
     *
     * @return string
     */
    protected function buildFragment(): string
    {
        return $this->fragment ? '#' . $this->fragment : '';
    }

    /**
     * Add an array of query string values.
     *
     * @param array $keys
     *
     * @return $this
     */
    protected function appendArray(array $keys)
    {
        foreach ($keys as $key => $value) {
            $this->addQuery($key, $value);
        }

        return $this;
    }

    /**
     * Determine if the given value is a valid page number.
     *
     * @param int $page
     *
     * @return bool
     */
    protected function isValidPageNumber(int $page): bool
    {
        return $page >= 1 && \filter_var($page, \FILTER_VALIDATE_INT) !== false;
    }

    /**
     * Resolve the current page or return the default value.
     *
     * @return int
     */
    protected function resolveCurrentPage(): int
    {
        $query = $this->request->getQueryParams();

        if (\array_key_exists($this->pageName, $query)) {
            $query = $this->secureInput($query);
            $page  = $query[$this->pageName];

            if ((int) $page >= 1 && \filter_var($page, \FILTER_VALIDATE_INT) !== false) {
                return (int) $page;
            }
        }

        return 1;
    }

    /**
     * At least check if the input string does not have null-byte
     * and is a UTF-8 valid string.
     *
     * @param array $query
     *
     * @return array
     */
    private function secureInput(array $query): array
    {
        $secure = static function (&$v): void {
            if (! \is_string($v) && ! \is_numeric($v)) {
                $v = '';
            } elseif (\mb_strpos($v, "\0") !== false) {
                $v = '';
            } elseif (! \mb_check_encoding($v, 'UTF-8')) {
                $v = '';
            }
        };

        \array_walk_recursive($query, $secure);

        return $query;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
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\Events\DataCollector\TraceableEventManager;
use Viserio\Component\Events\DataCollector\ViserioEventsDataCollector;
use Viserio\Component\Events\EventManager;
use Viserio\Component\OptionsResolver\Traits\OptionsResolverTrait;

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

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

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

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

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

    /**
     * Extend viserio events manager with a new event.
     *
     * @param \Psr\Container\ContainerInterface           $container
     * @param null|\Viserio\Component\Events\EventManager $eventManager
     *
     * @return null|\Viserio\Component\Contract\Events\EventManager|\Viserio\Component\Events\DataCollector\TraceableEventManager
     */
    public static function extendEventManager(
        ContainerInterface $container,
        ?EventManager $eventManager = null
    ): ?TraceableEventManager {
        if ($eventManager !== null) {
            $options = self::resolveOptions($container->get('config'));

            if ($options['collector']['events']) {
                $eventManager = new TraceableEventManager($eventManager, $container->get(Stopwatch::class));

                if ($container->has(LoggerInterface::class)) {
                    $eventManager->setLogger($container->get(LoggerInterface::class));
                }
            }
        }

        return $eventManager;
    }

    /**
     * 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) {
            $options = self::resolveOptions($container->get('config'));

            if ($options['collector']['events']) {
                $profiler->addCollector(new ViserioEventsDataCollector(
                    $container->get(EventManager::class)
                ));
            }
        }

        return $profiler;
    }
}


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Viserio\Component\Contract\Foundation\HttpKernel as HttpKernelContract;
use Viserio\Component\HttpFoundation\Exception\MaintenanceModeException;

class CheckForMaintenanceModeMiddleware implements MiddlewareInterface
{
    /**
     * The http kernel implementation.
     *
     * @var \Viserio\Component\Contract\Foundation\HttpKernel
     */
    protected $kernel;

    /**
     * Create a new maintenance check middleware instance.
     *
     * @param \Viserio\Component\Contract\Foundation\HttpKernel $kernel
     */
    public function __construct(HttpKernelContract $kernel)
    {
        $this->kernel = $kernel;
    }

    /**
     * {@inheritdoc}
     *
     * @throws \Viserio\Component\HttpFoundation\Exception\MaintenanceModeException
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        if ($this->kernel->isDownForMaintenance()) {
            $data = \json_decode(\file_get_contents($this->kernel->getStoragePath('framework' . \DIRECTORY_SEPARATOR . 'down')), true);

            throw new MaintenanceModeException((int) $data['time'], (int) $data['retry'], $data['message']);
        }

        return $handler->handle($request);
    }
}


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

use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Node;
use Twig\Node\TextNode;
use Twig\Source;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
use Viserio\Bridge\Twig\Node\TransNode;

/**
 * Token Parser for the 'trans' tag.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @copyright Copyright (c) 2004-2017 Fabien Potencier
 */
class TransTokenParser extends AbstractTokenParser
{
    /**
     * Parses a token and returns a node.
     *
     * @param \Twig\Token $token
     *
     * @throws \Twig\Error\SyntaxError
     *
     * @return \Twig\Node\Node
     */
    public function parse(Token $token): Node
    {
        $lineno = $token->getLine();
        $stream = $this->parser->getStream();

        $vars   = new ArrayExpression([], $lineno);
        $domain = null;
        $locale = null;

        if ($stream->test('with')) {
            // {% trans with vars %}
            $stream->next();
            $vars = $this->parser->getExpressionParser()->parseExpression();
        }

        if ($stream->test('from')) {
            // {% trans from "messages" %}
            $stream->next();
            $domain = $this->parser->getExpressionParser()->parseExpression();
        }

        if ($stream->test('into')) {
            // {% trans into "fr" %}
            $stream->next();
            $locale = $this->parser->getExpressionParser()->parseExpression();
        }

        // {% trans %}message{% endtrans %}
        $stream->expect(Token::BLOCK_END_TYPE);

        $body = $this->parser->subparse(static function ($token) {
            return $token->test(['endtrans']);
        }, true);

        if (! $body instanceof TextNode && ! $body instanceof AbstractExpression) {
            $name = $stream->getSourceContext()->getName();

            throw new SyntaxError(
                'A message inside a trans tag must be a simple text.',
                $body->getTemplateLine(),
                new Source(
                    $stream->__toString(),
                    $name
                )
            );
        }

        $stream->expect(Token::BLOCK_END_TYPE);

        return new TransNode($body, $domain, $vars, $locale, $lineno, $this->getTag());
    }

    /**
     * Gets the tag name associated with this token parser.
     *
     * @return string The tag name
     */
    public function getTag(): string
    {
        return 'trans';
    }
}


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

use Throwable;
use Viserio\Component\Contract\Parser\Exception\ParseException;
use Viserio\Component\Contract\Parser\Parser as ParserContract;

class SerializeParser implements ParserContract
{
    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        try {
            return \unserialize(\trim($payload), ['allowed_classes' => false]);
        } catch (Throwable $exception) {
            throw new ParseException([
                'message' => \sprintf('Failed to parse serialized Data; %s.', $exception->getMessage()),
            ]);
        }
    }
}


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

use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Loader\ArrayLoader;
use Twig\Source;
use UnexpectedValueException;
use Viserio\Component\Console\Command\AbstractCommand;

class LintCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'lint:twig';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'lint:twig
        [dir=* : Path to the template dir.]
        [--files=* : Lint multiple files. Relative to the view path.]
        [--directories=* : Lint multiple directories. Relative to the view path.]
        [--format=txt : The output format. Supports `txt` or `json`.]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Lints a templates and outputs encountered errors';

    /**
     * A twig instance.
     *
     * @var \Twig\Environment
     */
    private $environment;

    /**
     * Create a DebugCommand instance.
     *
     * @param \Twig\Environment $environment
     */
    public function __construct(Environment $environment)
    {
        parent::__construct();

        $this->environment = $environment;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        $files = $this->getFiles((array) $this->option('files'), (array) $this->option('directories'));

        // If no files are found.
        if (\count($files) === 0) {
            throw new RuntimeException('No twig files found.');
        }

        $details = [];

        foreach ($files as $file) {
            $details[] = $this->validate(\file_get_contents($file), $file);
        }

        return $this->display($details, $this->option('format'));
    }

    /**
     * Gets an array of files to lint.
     *
     * @param array $files       array of files to check
     * @param array $directories array of directories to get the files from
     *
     * @return array
     */
    protected function getFiles(array $files, array $directories): array
    {
        $foundFiles = [];

        /** @var \SplFileInfo $file */
        foreach ($this->getFinder($directories) as $file) {
            if (\count($files) !== 0 && ! \in_array($file->getFilename(), $files, true)) {
                continue;
            }

            $foundFiles[] = $file->getRealPath();
        }

        return $foundFiles;
    }

    /**
     * Get a finder instance of Twig files in the specified directories.
     *
     * @param array $paths paths to search for files in
     *
     * @return iterable
     */
    protected function getFinder(array $paths): iterable
    {
        $foundFiles = [];
        $baseDir    = (array) $this->argument('dir');

        foreach ($baseDir as $dir) {
            if (\count($paths) !== 0) {
                foreach ($paths as $path) {
                    $this->findTwigFiles($dir . \DIRECTORY_SEPARATOR . $path, $foundFiles);
                }
            } else {
                $this->findTwigFiles($dir, $foundFiles);
            }
        }

        return $foundFiles;
    }

    /**
     * Validate the template.
     *
     * @param string $template twig template
     * @param string $file
     *
     * @return array
     */
    protected function validate(string $template, string $file): array
    {
        $realLoader = $this->environment->getLoader();

        try {
            $temporaryLoader = new ArrayLoader([$file => $template]);

            $this->environment->setLoader($temporaryLoader);
            $nodeTree = $this->environment->parse($this->environment->tokenize(new Source($template, $file)));

            $this->environment->compile($nodeTree);
            $this->environment->setLoader($realLoader);
        } catch (Error $exception) {
            $this->environment->setLoader($realLoader);

            return [
                'template'  => $template,
                'file'      => $file,
                'valid'     => false,
                'exception' => $exception,
            ];
        }

        return [
            'template' => $template,
            'file'     => $file,
            'valid'    => true,
        ];
    }

    /**
     * Output the results of the linting.
     *
     * @param array  $details validation results from all linted files
     * @param string $format  Format to output the results in. Supports txt or json.
     *
     * @throws \InvalidArgumentException thrown for an unknown format
     *
     * @return int
     */
    protected function display(array $details, string $format = 'txt'): int
    {
        $verbose = $this->getOutput()->isVerbose();

        switch ($format) {
            case 'txt':
                return $this->displayText($details, $verbose);
            case 'json':
                return $this->displayJson($details);

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

    /**
     * Output the results as text.
     *
     * @param array $details validation results from all linted files
     * @param bool  $verbose
     *
     * @return int
     */
    protected function displayText(array $details, bool $verbose = false): int
    {
        $errors = 0;

        foreach ($details as $info) {
            if ($verbose && $info['valid']) {
                $file = ' in ' . $info['file'];
                $this->line('<info>OK</info>' . $file);
            } elseif (! $info['valid']) {
                $errors++;
                $this->renderException($info);
            }
        }

        if ($errors === 0) {
            $this->comment(\sprintf('All %d Twig files contain valid syntax.', \count($details)));
        } else {
            $this->warn(\sprintf('%d Twig files have valid syntax and %d contain errors.', \count($details) - $errors, $errors));
        }

        return \min($errors, 1);
    }

    /**
     * Output the results as json.
     *
     * @param array $details validation results from all linted files
     *
     * @return int
     */
    protected function displayJson(array $details): int
    {
        $errors = 0;

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

                unset($info['template']);

                if (! $info['valid']) {
                    $info['message'] = $info['exception']->getMessage();

                    unset($info['exception']);

                    $errors++;
                }
            }
        );

        $this->line(\json_encode($details, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));

        return \min($errors, 1);
    }

    /**
     * Output the error to the console.
     *
     * @param array $info details for the file that failed to be linted
     *
     * @return void
     */
    protected function renderException(array $info): void
    {
        $exception = $info['exception'];
        $line      = $exception->getTemplateLine();
        $lines     = $this->getContext($info['template'], $line);

        $this->line(\sprintf('<error>Fail</error> in %s (line %s)', $info['file'], $line));

        foreach ($lines as $no => $code) {
            $this->line(
                \sprintf(
                    '%s %-6s %s',
                    $no === $line ? '<error>>></error>' : '  ',
                    $no,
                    $code
                )
            );

            if ($no === $line) {
                $this->line(\sprintf('<error>>> %s</error> ', $exception->getRawMessage()));
            }
        }
    }

    /**
     * Grabs the surrounding lines around the exception.
     *
     * @param string     $template contents of Twig template
     * @param int|string $line     line where the exception occurred
     * @param int        $context  number of lines around the line where the exception occurred
     *
     * @return array
     */
    protected function getContext(string $template, $line, int $context = 3): array
    {
        $lines    = \explode("\n", $template);
        $position = \max(0, $line - $context);
        $max      = \min(\count($lines), $line - 1 + $context);
        $result   = [];

        while ($position < $max) {
            $result[$position + 1] = $lines[$position];
            $position++;
        }

        return $result;
    }

    /**
     * Undocumented function.
     *
     * @param string $dir
     * @param array  $foundFiles
     *
     * @throws \RuntimeException
     *
     * @return void
     */
    private function findTwigFiles(string $dir, array &$foundFiles): void
    {
        try {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
                RecursiveIteratorIterator::SELF_FIRST
            );
        } catch (UnexpectedValueException $exception) {
            throw new RuntimeException($exception->getMessage());
        }

        foreach ($iterator as $file) {
            if (\pathinfo($file->getRealPath(), \PATHINFO_EXTENSION) === 'twig') {
                $foundFiles[] = $file;
            }
        }
    }
}


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

class MiddlewareNameResolver
{
    /**
     * Resolve the middleware name to a class name(s).
     *
     * @param string $name
     * @param array  $map
     * @param array  $middlewareGroups
     * @param array  $disabledMiddleware
     *
     * @return array|string
     */
    public static function resolve(string $name, array $map, array $middlewareGroups, array $disabledMiddleware)
    {
        if (isset($disabledMiddleware[$name]) || \in_array($name, $disabledMiddleware, true)) {
            return [];
        }

        if (isset($middlewareGroups[$name])) {
            return self::parseMiddlewareGroup($name, $map, $middlewareGroups, $disabledMiddleware);
        }

        return $map[$name] ?? $name;
    }

    /**
     * Parse the middleware group and format it for usage.
     *
     * @param string $name
     * @param array  $map
     * @param array  $middlewareGroups
     * @param array  $disabledMiddleware
     *
     * @return array
     */
    protected static function parseMiddlewareGroup(
        string $name,
        array $map,
        array $middlewareGroups,
        array $disabledMiddleware
    ): array {
        $results = [];

        foreach ($middlewareGroups[$name] as $middleware) {
            $name = \is_object($middleware) ? \get_class($middleware) : $middleware;

            if (isset($disabledMiddleware[$name]) || \in_array($name, $disabledMiddleware, true)) {
                continue;
            }

            // If the middleware is another middleware group we will pull in the group and
            // merge its middleware into the results. This allows groups to conveniently
            // reference other groups without needing to repeat all their middleware.
            if (\is_string($middleware) && isset($middlewareGroups[$middleware])) {
                $results = \array_merge(
                    $results,
                    self::parseMiddlewareGroup($middleware, $map, $middlewareGroups, $disabledMiddleware)
                );

                continue;
            }

            // If this middleware is actually a route middleware, we will extract the full
            // class name out of the middleware list now. Then we'll add the parameters
            // back onto this class' name so the pipeline will properly extract them.
            if (isset($map[$name])) {
                $middleware = $map[$name];
            }

            $results[] = $middleware;
        }

        return $results;
    }
}


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

use ArrayAccess;
use Viserio\Component\Contract\OptionsResolver\DeprecatedOptions as DeprecatedOptionsContract;
use Viserio\Component\Contract\OptionsResolver\Exception\InvalidArgumentException;
use Viserio\Component\Contract\OptionsResolver\Exception\InvalidValidatorException;
use Viserio\Component\Contract\OptionsResolver\Exception\MandatoryOptionNotFoundException;
use Viserio\Component\Contract\OptionsResolver\Exception\OptionNotFoundException;
use Viserio\Component\Contract\OptionsResolver\Exception\UnexpectedValueException;
use Viserio\Component\Contract\OptionsResolver\ProvidesDefaultOptions as ProvidesDefaultOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfig as RequiresComponentConfigContract;
use Viserio\Component\Contract\OptionsResolver\RequiresComponentConfigId as RequiresComponentConfigIdContract;
use Viserio\Component\Contract\OptionsResolver\RequiresConfigId as RequiresConfigIdContract;
use Viserio\Component\Contract\OptionsResolver\RequiresMandatoryOptions as RequiresMandatoryOptionsContract;
use Viserio\Component\Contract\OptionsResolver\RequiresValidatedConfig as RequiresValidatedConfigContract;

trait OptionsResolverTrait
{
    /**
     * Map of types to a corresponding function.
     *
     * @var array
     */
    private static $defaultTypeMap = [
        'resource' => '\is_resource',
        'callable' => '\is_callable',
        'int'      => '\is_int',
        'integer'  => '\is_int',
        'bool'     => '\is_bool',
        'boolean'  => '\is_bool',
        'float'    => '\is_float',
        'string'   => '\is_string',
        'object'   => '\is_object',
        'array'    => '\is_array',
        'null'     => '\is_null',
    ];

    /**
     * Returns options based on getDimensions() like [vendor][package] if class implements RequiresComponentConfig
     * and can perform mandatory option checks if class implements RequiresMandatoryOptions. If the
     * ProvidesDefaultOptions interface is implemented, these options must be overridden by the provided config.
     * If you want to allow configurations for more then one instance use RequiresConfigId interface.
     *
     * The \Viserio\Component\Contract\OptionsResolver\RequiresConfigId interface is supported.
     *
     * @param array|\ArrayAccess $config
     * @param null|string        $configId Config name, must be provided if factory uses RequiresConfigId interface
     *
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\InvalidArgumentException         If the $configId parameter is provided but factory does not support it
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\UnexpectedValueException         If the $config parameter has the wrong type
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\OptionNotFoundException          If no options are available
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\MandatoryOptionNotFoundException If a mandatory option is missing
     *
     * @return array
     */
    public static function resolveOptions($config, string $configId = null): array
    {
        $configClass = self::getConfigClass();
        $interfaces  = \class_implements($configClass);
        $dimensions  = [];

        if (isset($interfaces[RequiresComponentConfigContract::class])) {
            $dimensions = $configClass::getDimensions();
        }

        if (isset($interfaces[RequiresConfigIdContract::class]) || isset($interfaces[RequiresComponentConfigIdContract::class])) {
            $dimensions[] = $configId;
        } elseif ($configId !== null) {
            throw new InvalidArgumentException(
                \sprintf('The factory [%s] does not support multiple instances.', $configClass)
            );
        }

        // get configuration for provided dimensions
        $config = self::getConfigurationDimensions($dimensions, $config, $configClass, $interfaces, $configId);

        if ((array) $config !== $config &&
            ! $config instanceof ArrayAccess &&
            isset($interfaces[RequiresComponentConfigIdContract::class])
        ) {
            throw new UnexpectedValueException($configClass::getDimensions());
        }

        if (isset($interfaces[RequiresMandatoryOptionsContract::class])) {
            self::checkMandatoryOptions($configClass, $configClass::getMandatoryOptions(), $config, $interfaces);
        }

        if (isset($interfaces[RequiresValidatedConfigContract::class])) {
            self::validateOptions($configClass::getOptionValidators(), $config, $configClass);
        }

        if (isset($interfaces[ProvidesDefaultOptionsContract::class])) {
            $config = \array_replace_recursive($configClass::getDefaultOptions(), (array) $config);
        }

        if (isset($interfaces[DeprecatedOptionsContract::class])) {
            self::checkDeprecatedOptions($configClass, $configClass::getDeprecatedOptions(), $config);
        }

        return (array) $config;
    }

    /**
     * The configurable class.
     *
     * @return string
     */
    protected static function getConfigClass(): string
    {
        return static::class;
    }

    /**
     * Checks if a mandatory param is missing, supports recursion.
     *
     * @param string             $configClass
     * @param array              $mandatoryOptions
     * @param array|\ArrayAccess $config
     * @param array              $interfaces
     *
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\MandatoryOptionNotFoundException
     *
     * @return void
     */
    private static function checkMandatoryOptions(
        string $configClass,
        array $mandatoryOptions,
        $config,
        array $interfaces
    ): void {
        foreach ($mandatoryOptions as $key => $mandatoryOption) {
            $useRecursion = ! \is_scalar($mandatoryOption);

            if (! $useRecursion && \array_key_exists($mandatoryOption, (array) $config)) {
                continue;
            }

            if ($useRecursion && isset($config[$key])) {
                self::checkMandatoryOptions($configClass, $mandatoryOption, $config[$key], $interfaces);

                return;
            }

            throw new MandatoryOptionNotFoundException(
                isset($interfaces[RequiresComponentConfigContract::class]) ? $configClass::getDimensions() : [],
                $useRecursion ? $key : $mandatoryOption
            );
        }
    }

    /**
     * Get configuration for provided dimensions.
     *
     * @param array              $dimensions
     * @param array|\ArrayAccess $config
     * @param string             $configClass
     * @param null|string        $configId
     * @param array              $interfaces
     *
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\OptionNotFoundException
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\UnexpectedValueException
     *
     * @return array|\ArrayAccess
     */
    private static function getConfigurationDimensions(
        array $dimensions,
        $config,
        string $configClass,
        array $interfaces,
        ?string $configId
    ) {
        foreach ($dimensions as $dimension) {
            if ((array) $config !== $config && ! $config instanceof ArrayAccess) {
                throw new UnexpectedValueException($dimensions, $dimension);
            }

            if (! isset($config[$dimension])) {
                if (! isset($interfaces[RequiresMandatoryOptionsContract::class]) &&
                    isset($interfaces[ProvidesDefaultOptionsContract::class])
                ) {
                    break;
                }

                throw new OptionNotFoundException($configClass, $dimension, $configId);
            }

            $config = $config[$dimension];
        }

        return $config;
    }

    /**
     * Run a validator against given config.
     *
     * @param array<string => array<int => string>, string => callback> $validators
     * @param array|\ArrayAccess                                        $config
     * @param string                                                    $configClass
     *
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\InvalidValidatorException
     *
     * @return void
     */
    private static function validateOptions(array $validators, $config, string $configClass): void
    {
        foreach ($validators as $key => $values) {
            if (! \array_key_exists($key, (array) $config)) {
                continue;
            }

            if (\is_array($values) && isset($values[0]) && \is_string($values[0])) {
                $hasError = false;

                foreach ($values as $check) {
                    if ($hasError === false && \array_key_exists($check, self::$defaultTypeMap)) {
                        $fn       = self::$defaultTypeMap[$check];
                        $hasError = $fn($config[$key]);
                    }
                }

                if (! $hasError) {
                    throw InvalidArgumentException::invalidType($key, $config[$key], $values, $configClass);
                }

                continue;
            }

            if (\is_callable($values)) {
                $values($config[$key], $key);

                continue;
            }

            if (! \is_array($values) && ! \is_callable($values)) {
                throw new InvalidValidatorException(\sprintf(
                    'The validator must be of type callable or string[]; [%s] given, in [%s].',
                    \is_object($values) ? \get_class($values) : \gettype($values),
                    $configClass
                ));
            }

            self::validateOptions((array) $values, $config[$key], $configClass);
        }
    }

    /**
     * Checks if a deprecation exists and triggers a deprecation error.
     *
     * @param string             $configClass
     * @param array              $deprecatedOptions
     * @param array|\ArrayAccess $config
     *
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\InvalidArgumentException If deprecation message cant be found or
     *                                                                                the key don't exists in the config array
     *
     * @return void
     */
    private static function checkDeprecatedOptions(string $configClass, array $deprecatedOptions, $config): void
    {
        foreach ($deprecatedOptions as $key => $deprecationMessage) {
            if (\is_array($deprecationMessage)) {
                self::checkDeprecatedOptions($configClass, $deprecationMessage, $config[$key]);

                continue;
            }

            if (\is_int($key)) {
                $key                = $deprecationMessage;
                $deprecationMessage = 'The option [%s] is deprecated.';
            }

            if (! \array_key_exists($key, (array) $config)) {
                throw new InvalidArgumentException(\sprintf(
                    'Option [%s] cant be deprecated, because it does not exist, in [%s].',
                    $key,
                    $configClass
                ));
            }

            if (! \is_string($deprecationMessage)) {
                throw new InvalidArgumentException(\sprintf(
                    'Invalid deprecation message value provided for [%s]; Expected [string], but got [%s], in [%s].',
                    $key,
                    (\is_object($deprecationMessage) ? \get_class($deprecationMessage) : \gettype($deprecationMessage)),
                    $configClass
                ));
            }

            if (empty($deprecationMessage)) {
                throw new InvalidArgumentException(\sprintf(
                    'Deprecation message cant be empty, for option [%s], in [%s].',
                    $key,
                    $configClass
                ));
            }

            @\trigger_error(\sprintf($deprecationMessage, $key), \E_USER_DEPRECATED);
        }
    }
}


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Swift_Mailer;
use Swift_Plugins_MessageLogger;
use Viserio\Component\Contract\Profiler\PanelAware as PanelAwareContract;
use Viserio\Component\Profiler\DataCollector\AbstractDataCollector;

class SwiftMailDataCollector extends AbstractDataCollector implements PanelAwareContract
{
    /**
     * Swift_Plugins_MessageLogger instance.
     *
     * @var \Swift_Plugins_MessageLogger
     */
    protected $messagesLogger;

    /**
     * Create new swift mailer data collector instance.
     *
     * @param \Swift_Mailer $mailer
     */
    public function __construct(Swift_Mailer $mailer)
    {
        $this->messagesLogger = new Swift_Plugins_MessageLogger();

        $mailer->registerPlugin($this->messagesLogger);
    }

    /**
     * {@inheritdoc}
     */
    public function collect(ServerRequestInterface $serverRequest, ResponseInterface $response): void
    {
        $mails = [];

        foreach ($this->messagesLogger->getMessages() as $message) {
            $mails[] = [
                'to'      => $this->formatTo($message->getTo()),
                'subject' => $message->getSubject(),
                'headers' => $message->getHeaders()->toString(),
            ];
        }

        $this->data = [
            'count' => \count($mails),
            'mails' => $mails,
        ];
    }

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

    /**
     * {@inheritdoc}
     */
    public function getPanel(): string
    {
        return $this->createTable(
            $this->data['mails'],
            ['headers' => ['to', 'subject', 'headers']]
        );
    }

    /**
     * Format to from message.
     *
     * @param null|array $to
     *
     * @return string
     */
    protected function formatTo(?array $to): string
    {
        if (! $to) {
            return '';
        }

        $f = [];

        foreach ($to as $k => $v) {
            $f[] = (empty($v) ? '' : "${v} ") . "<${k}>";
        }

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


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

use Symfony\Component\Yaml\Exception\ParseException as YamlParseException;
use Symfony\Component\Yaml\Parser as SymfonyYamlParser;
use Symfony\Component\Yaml\Yaml as SymfonyYaml;
use Viserio\Component\Contract\Parser\Exception\ParseException;
use Viserio\Component\Contract\Parser\Exception\RuntimeException;
use Viserio\Component\Contract\Parser\Parser as ParserContract;

class YamlParser implements ParserContract
{
    /**
     * Bit to customize the YAML parser.
     *
     * @var int
     */
    protected $flags = 0;

    /**
     * A Yaml parser instance.
     *
     * @var \Symfony\Component\Yaml\Parser
     */
    private $parser;

    /**
     * Create a new Yaml parser.
     *
     * @throws \RuntimeException
     */
    public function __construct()
    {
        // @codeCoverageIgnoreStart
        if (! \class_exists(SymfonyYaml::class)) {
            throw new RuntimeException('Unable to read yaml as the Symfony Yaml Component is not installed.');
        }
        // @codeCoverageIgnoreEnd

        $this->parser = new SymfonyYamlParser();
    }

    /**
     * A bit field of PARSE_* constants to customize the YAML parser behavior.
     *
     * @param int $flags
     */
    public function setFlags(int $flags): void
    {
        $this->flags = $flags;
    }

    /**
     * {@inheritdoc}
     */
    public function parse(string $payload): array
    {
        try {
            return $this->parser->parse(\trim(\preg_replace('/\t+/', '', $payload)), $this->flags);
        } catch (YamlParseException $exception) {
            throw new ParseException([
                'message'   => $exception->getMessage(),
                'exception' => $exception,
            ]);
        }
    }
}