<?php

/**
 * @author     Laurent Jouanneau
 * @copyright  2008-2017 Laurent Jouanneau
 *
 * @link       http://jelix.org
 * @licence    http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public Licence, see LICENCE file
 */

namespace Jelix\IniFile;

/**
 * utility class to read an ini file by preserving comments, whitespace..
 * It follows same behaviors of parse_ini_file, except when there are quotes
 * inside values. it doesn't support quotes inside values, because parse_ini_file
 * is totally bugged, depending cases.
 *
 * Use it only with IniModifierArray or MultiIniModifier. If you only want to
 * read ini content, use parse_ini_file, it's better in term of performance.
 */
class IniReader implements IniReaderInterface
{
    /**
     * @const integer token type for whitespaces
     */
    const TK_WS = 0;
    /**
     * @const integer token type for a comment
     */
    const TK_COMMENT = 1;
    /**
     * @const integer token type for a section header
     */
    const TK_SECTION = 2;
    /**
     * @const integer token type for a simple value
     */
    const TK_VALUE = 3;
    /**
     * @const integer token type for a value of an array item
     */
    const TK_ARR_VALUE = 4;

    /**
     * each item of this array contains data for a section. the key of the item
     * is the section name. There is a section with the key "0", and which contains
     * data for options which are not in a section.
     * each value of the items is an array of tokens. A token is an array with
     * some values. first value is the token type (see TK_* constants), and other
     * values depends of the token type:
     * - TK_WS: content of whitespaces
     * - TK_COMMENT: the comment
     * - TK_SECTION: the section name
     * - TK_VALUE: the name, and the value
     * - TK_ARRAY_VALUE: the name, the value, and the key.
     *
     * @var array
     */
    protected $content = array();

    /**
     * @var string the filename of the ini file
     */
    protected $filename = '';

    /**
     * load the given ini file.
     *
     * @param string $filename the file to load
     */
    public function __construct($filename)
    {
        if (!file_exists($filename) || !is_file($filename)) {
            throw new IniInvalidArgumentException("The file $filename does not exists");
        }
        $this->filename = $filename;
        $this->parse(preg_split("/(\r\n|\n|\r)/", file_get_contents($filename)));
    }

    public function isEmpty()
    {
        return (count($this->content) == 0);
    }

    /**
     * @return string the file name
     */
    public function getFileName()
    {
        return $this->filename;
    }

    /**
     * parsed the lines of the ini file.
     */
    protected function parse($lines)
    {
        $this->content = array(0 => array());
        $currentSection = 0;
        $multiline = false;
        $currentValue = null;

        $arrayContents = array();

        foreach ($lines as $num => $line) {
            if ($multiline) {
                if (preg_match('/^(.*)"\s*$/', $line, $m)) {
                    $currentValue[2] .= $m[1];
                    $multiline = false;
                    $this->content[$currentSection][] = $currentValue;
                } else {
                    $currentValue[2] .= $line."\n";
                }
            } elseif (preg_match('/^\s*([\\w0-9_.\\-]+)(\\[[^\\[\\]]*\\])?\s*=\s*(")?([^"]*)(")?(\s*)/ui', $line, $m)) {
                list($all, $name, $foundkey, $firstquote, $value, $secondquote, $lastspace) = $m;

                if ($foundkey != '') {
                    $key = substr($foundkey, 1, -1);
                    if ($key == '') {
                        if (isset($arrayContents[$currentSection][$name])) {
                            $key = count($arrayContents[$currentSection][$name]);
                        } else {
                            $key = 0;
                        }
                    }
                    $currentValue = array(self::TK_ARR_VALUE, $name, $value, $key);
                    $arrayContents[$currentSection][$name][$key] = $value;
                } else {
                    $currentValue = array(self::TK_VALUE, $name, $value);
                }

                if ($firstquote == '"' && $secondquote == '') {
                    $multiline = true;
                    $currentValue[2] .= "\n";
                } else {
                    if ($firstquote == '' && $secondquote == '') {
                        $currentValue[2] = trim($value);
                    }
                    $this->content[$currentSection][] = $currentValue;
                }
            } elseif (preg_match('/^(\\s*;.*)$/', $line, $m)) {
                $this->content[$currentSection][] = array(self::TK_COMMENT, $m[1]);
            } elseif (preg_match('/^(\\s*\\[([^\\]]+)\\]\\s*)/ui', $line, $m)) {
                if (strpos($m[2], ';')) {
                    // ';' is forbidden in the name as it begins a comment
                    throw new IniSyntaxException("Invalid syntax for the section name: \"".$m[2].'"');
                }
                $currentSection = $m[2];
                $this->content[$currentSection] = array(
                    array(self::TK_SECTION, $m[1]),
                );
            } else {
                $this->content[$currentSection][] = array(self::TK_WS, $line);
            }
        }
    }

    /**
     * return the value of an option in the ini file. If the option doesn't exist,
     * it returns null.
     *
     * @param string $name    the name of the option to retrieve
     * @param string $section the section where the option is. 0 is the global section
     * @param int    $key     for option which is an item of array, the key in the array
     *
     * @return mixed|null the value
     */
    public function getValue($name, $section = 0, $key = null)
    {
        if (!isset($this->content[$section])) {
            return null;
        }
        $arrayValue = array();
        $isArray = false;
        foreach ($this->content[$section] as $k => $item) {
            if (($item[0] != self::TK_VALUE && $item[0] != self::TK_ARR_VALUE)
                || $item[1] != $name) {
                continue;
            }
            if ($item[0] == self::TK_ARR_VALUE) {
                if ($key !== null) {
                    if ($item[3] != $key) {
                        continue;
                    }
                } else {
                    $isArray = true;
                    $arrayValue[] = $this->convertValue($item[2]);
                    continue;
                }
            }
            return $this->convertValue($item[2]);
        }
        if ($isArray) {
            return $arrayValue;
        }

        return null;
    }

    /**
     * return all values of a section in the ini file.
     *
     * @param string $section the section from which we want values. 0 is the global section
     *
     * @return array the list of values, $key=>$value
     */
    public function getValues($section = 0)
    {
        if (!isset($this->content[$section])) {
            return array();
        }
        $values = array();
        foreach ($this->content[$section] as $k => $item) {
            if ($item[0] != self::TK_VALUE && $item[0] != self::TK_ARR_VALUE) {
                continue;
            }

            $val = $this->convertValue($item[2]);

            if ($item[0] == self::TK_VALUE) {
                $values[$item[1]] = $val;
            } else {
                $values[$item[1]][$item[3]] = $val;
            }
        }

        return $values;
    }

    protected function convertValue($value) {
        if (preg_match('/^-?[0-9]$/', $value)) {
            return intval($value);
        } elseif (preg_match('/^-?[0-9\.]$/', $value)) {
            return floatval($value);
        } elseif (strtolower($value) === 'true' || strtolower($value) === 'on' || strtolower($value) === 'yes') {
            return true;
        } elseif (strtolower($value) === 'false' || strtolower($value) === 'off' || strtolower($value) === 'no' || strtolower($value) === 'none') {
            return false;
        }
        return $value;
    }

    /**
     * says if there is a section with the given name.
     *
     * @since 1.2
     */
    public function isSection($name)
    {
        return isset($this->content[$name]);
    }

    /**
     * return the list of section names.
     *
     * @return array
     *
     * @since 1.2
     */
    public function getSectionList()
    {
        $list = array_keys($this->content);
        array_shift($list); // remove the global section
        return $list;
    }
}


<?php
namespace Tuck\ConverterBundle\Loader;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Loader\LoaderInterface;
use Tuck\ConverterBundle\Exception\UnknownFormatException;

/**
 * Factory for the standard set of loaders Symfony comes with
 *
 * @author Ross Tuck <me@rosstuck.com>
 */
class StandardLoaderFactory implements LoaderFactory
{
    protected $shortTypeToClassMapping = array(
        'xml' => 'Symfony\Component\DependencyInjection\Loader\XmlFileLoader',
        'yaml' => 'Symfony\Component\DependencyInjection\Loader\YamlFileLoader',
        'yml' => 'Symfony\Component\DependencyInjection\Loader\YamlFileLoader',
        'php' => 'Symfony\Component\DependencyInjection\Loader\PhpFileLoader',
        'ini' => 'Symfony\Component\DependencyInjection\Loader\IniFileLoader'
    );

    /**
     * Creates a loader for the service config file
     *
     * @param  string           $type      The name of the file type, such as xml, yml or php
     * @param  ContainerBuilder $container
     * @param  string           $path      The path to the *directory* containing the the file
     * @return LoaderInterface
     */
    public function createFileLoader($type, ContainerBuilder $container, $path)
    {
        $className = $this->getClassNameByShortType($type);

        return new $className($container, new FileLocator($path));
    }

    /**
     * Get loader class name based on short name
     * @param  string                 $type
     * @return string                 Loader class name
     * @throws UnknownFormatException
     */
    protected function getClassNameByShortType($type)
    {
        if (!isset($this->shortTypeToClassMapping[$type])) {
            throw UnknownFormatException::create($type);
        }

        return $this->shortTypeToClassMapping[$type];
    }
}


<?php

namespace SessionHandler\Storage;

class SecureCookie
{
    private $hash_len;
    private $hash_algo;
    private $hash_secret;
    const DEFAULT_EXPIRATION_DATE = 30;

    /**
     * Initializes a new Secure Cookie.
     *
     * @param string $secret secret to sign the cookie with
     * @param string $len the length of the hash
     * @param string $algo the algorithm to pass to hash_hmac
     *
     * @return SecureCookie
     */
    public function __construct($secret = null, $len = 128, $algo = "sha512")
    {
        $this->hash_len = $len;
        $this->hash_algo = $algo;

        // If hash secret is empty, we need to set a default one
        if (empty($secret)) {
            $secret = $this->default_hash_secret();
        }

        $this->hash_secret = $secret;
    }

    /**
     * Calculates the default_hash_secret to use as a fallback if no secret is passed. It's
     * a bad idea to rely on this in production.
     */
    protected function default_hash_secret()
    {
        // This is not good, it's easily leakable to the outside world,
        // but it's predictable and doesn't require much server state. It's a bad
        // idea to depend on this and probably won't work with multiple servers or
        // with multiple PHP-FPM/Apache processes.
        return md5(php_uname() . getmypid());
    }

    public function get($name, $default = null)
    {
        if (!$this->has($name)) {
            return $default;
        }

        $raw = base64_decode($_COOKIE[$name]);

        // Cookie should be at least the size of the hash length.
        // If it's not, we can just bail out
        if (strlen($raw) < $this->hash_len) {
            return $default;
        }

        // The cookie data contains the actual data w/ the hash concatonated to the end,
        // since the hash is a fixed length, we can extract the last hash_length chars
        // to get the hash.
        $hash = substr($raw, strlen($raw) - $this->hash_len, $this->hash_len);
        $data = substr($raw, 0, -($this->hash_len));

        // Calculate what the hash should be, based on the data. If the data has not been
        // tampered with, $hash and $hash_calculated will be the same
        $hash_calculated = hash_hmac($this->hash_algo, $data, $this->hash_secret);

        // If we calculate a different hash, we can't trust the data.
        if ($hash_calculated !== $hash) {
            throw new HashMismatchException();
        }

        return $data;
    }

    /**
     * Checks if a cookie exists or not.
     *
     * @param string $name name of the cookie to check for
     *
     * @return true if the cookie exists, false otherwise
     */
    public function has($name)
    {
        return isset($_COOKIE[$name]);
    }

    /**
     * Set cookie.
     *
     * @param string $name
     * @param string $value
     * @param int|null $minutes If null, we set the value to `0` (which means the cookie lives
     *                          within this client session), otherwise the timestamp is calculated
     *                          as `current time + $minutes * 60`
     * @param string|null $path
     * @param string|null $domain
     * @param bool $secure
     * @param bool $httponly
     * @return bool
     */
    public function make($name, $value, $minutes = null, $path = null, $domain = null, $secure = false, $httponly = true)
    {
        // Calculate a hash for the data and append it to the end of the data string
        $hash = hash_hmac($this->hash_algo, $value, $this->hash_secret);
        $value .= $hash;
        $minutes = $minutes ? $minutes : self::DEFAULT_EXPIRATION_DATE;
        // Set a cookie with the data
        $ttl = $minutes === null ? 0 : time() + ($minutes * 60);
        return setcookie($name, base64_encode($value), $ttl, $path, $domain, $secure, $httponly);
    }

    public function forget($name)
    {
        setcookie( $name, '', time() - 1000 );
        setcookie( $name, '', time() - 1000, '/' );
    }

}

class HashMismatchException extends \Exception
{

}


<?php
namespace Tuck\ConverterBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use \SplFileInfo;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Tuck\ConverterBundle\Exception\UnknownBundleException;
use Tuck\ConverterBundle\Exception\EmptyDirectoryException;

/**
 * Converts a service config file to another format.
 *
 * @author Ross Tuck <me@rosstuck.com>
 */
class ConvertCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
            ->setName('container:convert')
            ->setDescription('Convert a service container config from one format to another')
            ->addArgument('format', InputArgument::REQUIRED, 'The format to convert to (yml, xml, graphviz, php)')
            ->addArgument('file', InputArgument::OPTIONAL, 'Source file to convert')
            ->addOption('output', 'o', InputOption::VALUE_NONE, 'Echo the new config instead of writing it to a file');
    }

    /**
     * Interactively determine the file and convert it
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $fileToConvert = $this->determineFile($output, $input->getArgument('file'));
        $newFormat = $input->getArgument('format');

        $newConfig = $this
            ->getContainer()
            ->get('tuck_converter.config_format_converter')
            ->convertFile($fileToConvert, $newFormat);

        if ($input->getOption('output')) {
            $output->write($newConfig);
        } else {
            $this->writeToFile($output, $fileToConvert, $newConfig, $newFormat);
        }
    }

    /**
     * Choose file from cli input or interactively
     *
     * @param  OutputInterface $output
     * @param  string          $givenFileName
     * @return SplFileInfo
     */
    protected function determineFile(OutputInterface $output, $givenFileName)
    {
        if (is_file($givenFileName)) {
            return new \SplFileInfo($givenFileName);
        }

        return $this->chooseFileInDirectory(
            $output,
            $this->chooseBundle($output)->getPath().'/Resources/config/'
        );
    }

    /**
     * Prompt the user to choose the bundle whose config they want to convert
     *
     * @param  OutputInterface        $output
     * @return Bundle
     * @throws UnknownBundleException
     */
    protected function chooseBundle(OutputInterface $output)
    {
        $bundles = $this->getContainer()->get('kernel')->getBundles();

        $selectedBundle = $this->getHelperSet()->get('dialog')->ask(
            $output,
            '<info>Which bundle\'s config would you like to convert? </info>',
            null,
            array_keys($bundles)
        );

        if (!isset($bundles[$selectedBundle])) {
            throw UnknownBundleException::create($selectedBundle);
        }

        return $bundles[$selectedBundle];
    }

    /**
     *
     * @param  OutputInterface         $output
     * @param  string                  $path
     * @return SplFileInfo
     * @throws EmptyDirectoryException
     */
    protected function chooseFileInDirectory(OutputInterface $output, $path)
    {
        // Gather files in directory
        $files = array();
        foreach (new \FilesystemIterator($path) as $file) {
            $files[] = $file;
        }

        // Handle edge cases
        if (count($files) === 0) {
            throw EmptyDirectoryException::create($path);
        } elseif (count($files) === 1) {
            return current($files);
        }

        // Prompt user for which file
        $fileIndex = $this->getHelperSet()->get('dialog')->select(
            $output,
            '<info>Which file should be converted? </info>',
            array_map(
                function ($fileInfo) {
                    return $fileInfo->getFilename();
                },
                $files
            )
        );

        return $files[$fileIndex];
    }

    /**
     * Write the new config to the same directory as the original file
     *
     * @param OutputInterface $output
     * @param SplFileInfo     $originalFile
     * @param string          $newConfig
     * @param string          $newFormat    The short file name given, like
     */
    protected function writeToFile(OutputInterface $output, SplFileInfo $originalFile, $newConfig, $newFormat)
    {
        $proposedLocation = $originalFile->getPath().'/services.'.$newFormat;
        $location = $this->getHelperSet()->get('dialog')->ask(
            $output,
            "<info>Where should the new config be written?</info> [{$proposedLocation}] ",
            $proposedLocation
        );

        if (file_exists($location)
            && !$this->getHelperSet()->get('dialog')->askConfirmation($output, 'File exists. Overwrite? ')
        ) {
            $output->writeln('Aborting...');

            return;
        }

        file_put_contents($location, $newConfig);
        $output->writeln(
            "Written! Don't forget to update the DependencyInjection/*Extension class in your bundle to use the new ".
            "loader class and delete the old config file."
        );
    }
}


<?php
namespace Tuck\ConverterBundle\Dumper;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\DumperInterface;
use Tuck\ConverterBundle\Exception\UnknownFormatException;

/**
 * Factory for Symfony's standard set of Dumpers
 *
 * @author Ross Tuck <me@rosstuck.com>
 */
class StandardDumperFactory implements DumperFactory
{
    /**
     * @var array
     */
    protected $shortTypeToClassMapping = array(
        'xml' => 'Symfony\Component\DependencyInjection\Dumper\XmlDumper',
        'yaml' => 'Symfony\Component\DependencyInjection\Dumper\YamlDumper',
        'yml' => 'Symfony\Component\DependencyInjection\Dumper\YamlDumper',
        'php' => 'Symfony\Component\DependencyInjection\Dumper\PhpDumper',
        'gv' => 'Symfony\Component\DependencyInjection\Dumper\GraphvizDumper',
    );

    /**
     * Creates a dumper for use only by the given container
     *
     * @param  string           $type
     * @param  ContainerBuilder $container
     * @return DumperInterface
     */
    public function createDumper($type, ContainerBuilder $container)
    {
        $className = $this->getClassNameByShortType($type);

        return new $className($container);
    }

    /**
     * Get class name based on format's short name
     * @param $type
     * @return mixed
     * @throws UnknownFormatException
     */
    protected function getClassNameByShortType($type)
    {
        if (!isset($this->shortTypeToClassMapping[$type])) {
            throw UnknownFormatException::create($type);
        }

        return $this->shortTypeToClassMapping[$type];
    }
}


<?php

namespace Tuck\ConverterBundle\File;

use Tuck\ConverterBundle\Exception\FileAlreadyExistsException;

/**
 * Creates temp files in the System tmp dir
 *
 * @author Ross Tuck <me@rosstuck.com>
 */
class SysTempFileFactory implements TempFileFactory
{
    /**
     * Essentially, an "improved" tempnam().
     *
     * As compared to the core tempnam, this version allows you to add a file
     * extension, returns an SplFileObject and has a short hand for priming it
     * with some content. If content is written, the file is not rewound before
     * returning.
     *
     * @param  string|null    $content
     * @param  string         $extension
     * @return \SplFileObject
     */
    public function createFile($content = null, $extension = 'tmp')
    {
        $filename = $this->generateFilename($extension);
        if (file_exists($filename)) {
            throw FileAlreadyExistsException::create($filename);
        }

        $tempFile = new \SplFileObject($filename, 'x+');
        if ($content !== null) {
            $tempFile->fwrite($content);
            $tempFile->fflush();
        }

        return $tempFile;
    }

    /**
     * Generates a reasonably unique path for a temp file
     *
     * @param  string $extension
     * @return string
     */
    protected function generateFilename($extension)
    {
        return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'tuck_converter_bundle_' . uniqid() . '.' . $extension;
    }
}


<?php
namespace Tuck\ConverterBundle;

use SplFileInfo;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Tuck\ConverterBundle\Dumper\DumperFactory;
use Tuck\ConverterBundle\Exception\UnknownFileException;
use Tuck\ConverterBundle\File\TempFileFactory;
use Tuck\ConverterBundle\Loader\LoaderFactory;

/**
 * Converts a services config file to another format.
 *
 * Future changes:
 * This class exposes the file part rather heavily because that's how it is
 * in practice but also because that's how the internal loader and dumpers
 * work.
 *
 * This would probably be improved by refactoring it to return an actual
 * ConfigFile object, then we could break the coupling with file extensions
 * in the UI.
 *
 * @author Ross Tuck <me@rosstuck.com>
 */
class ConfigFormatConverter
{
    /**
     * @var LoaderFactory
     */
    protected $loaderFactory;

    /**
     * @var DumperFactory
     */
    protected $dumperFactory;

    /**
     * @var TempFileFactory
     */
    protected $tempFileFactory;

    public function __construct(
        LoaderFactory $loaderFactory,
        DumperFactory $dumperFactory,
        TempFileFactory $tempFileFactory
    ) {
        $this->loaderFactory = $loaderFactory;
        $this->dumperFactory = $dumperFactory;
        $this->tempFileFactory = $tempFileFactory;
    }

    /**
     * Convert a service config file from one format to another (xml, yml, etc)
     *
     * @param  SplFileInfo                    $file      The file to convert
     * @param  string                         $newFormat Format to convert to, given as a file extension
     * @return string                         Converted config, returned as a raw string
     * @throws Exception\UnknownFileException
     */
    public function convertFile(SplFileInfo $file, $newFormat)
    {
        if (!$file->isFile()) {
            throw UnknownFileException::create($file->getRealPath());
        }
        $container = new ContainerBuilder(new ParameterBag());
        $loader = $this->loaderFactory->createFileLoader($file->getExtension(), $container, $file->getPath());
        $loader->load($file->getFilename());

        return $this->dumperFactory->createDumper($newFormat, $container)->dump();
    }

    /**
     * Convert a config represented as a string to some other format
     *
     * @param  string $content
     * @param  string $oldFormat
     * @param  string $newFormat
     *
     * @throws \Exception when file conversion fails
     *
     * @return string
     */
    public function convertString($content, $oldFormat, $newFormat)
    {
        $tempFile = $this->tempFileFactory->createFile($content, $oldFormat);

        try {
            $output = $this->convertFile($tempFile, $newFormat);
        } catch (\Exception $e) {
            // Cleanup the temp file, even with a failure
            unlink($tempFile->getRealPath());
            throw $e;
        }
        unlink($tempFile->getRealPath());

        return $output;
    }
}


<?php

namespace SessionHandler;

/*
 * Allow session data to be stored in the cookie securely, which scales
 * nicely. Cookie storage is abstracted out to SecureCookie.php
 *
 *
 * http://php.net/manual/en/class.sessionhandlerinterface.php
 */

class Cookie implements \SessionHandlerInterface {

  private $storage = null;

  /**
   * Initializes a new Cookie session handler and configures the hashing algorithm.
   * @param string $hash_secret secret to sign the cookie with
   * @param string $hash_len the length of the hash
   * @param string $hash_algo the algorithm to pass to hash_hmac
   * @return Cookie
   */
  public function __construct($secret=null) {
    $this->storage = new Storage\SecureCookie($secret);
  }

  /**
   * Set a custom storage handler
   * @param Storage $storage storage handler
   */
  public function setStorage($storage) {
    $this->storage = $storage;
  }

  /**
   * Called by PHP to read session data.
   * @param string $session_id
   * @return string serialized session data
   */
  public function read($session_id) {

    // Check for the existance of a cookie with the name of the session id
    // Make sure that the cookie is atleast the size of our hash, otherwise it's invalid
    // Return an empty string if it's invalid.
    if (! $this->storage->has($session_id)) return '';

    try {
      $data = $this->storage->get($session_id);
    } catch (HashMismatchException $ex) {
      $data = '';
    }

    // Return the data, now that it's been verified.
    return $data;
  }

  /**
   * Called by PHP to write out session data
   * @param string $session_id
   * @param string $data
   * @return bool write succeeded
   */
  public function write($session_id, $data) {
    $this->storage->make($session_id, $data);
  }

  /**
   * Called by PHP to destroy the session
   * @param string $session_id
   * @return bool true success
   */
  public function destroy($session_id) {
    $this->storage->forget($session_id);
  }

  // In the context of cookies, these three methods are unneccessary, but must
  // be implemented as part of the SessionHandlerInterface.
  public function open($save_path, $name) { return true; }
  public function gc($maxlifetime) { return true; }
  public function close() { return true; }

}


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

use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Contract\Routing\Route as RouteContract;
use Viserio\Component\Contract\Routing\Router as RouterContract;

class RouteListCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'route:table';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'route:table
        [--method= : Filter the routes by method.]
        [--name= : Filter the routes by name.]
        [--path= : Filter the routes by path.]
        [-r|--reverse : Reverse the ordering of the routes.]
        [--sort=uri : The column (host, method, uri, name, action) to sort by.]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Table of all registered routes';

    /**
     * The table headers for the command.
     *
     * @var array
     */
    protected static $headers = ['method', 'uri', 'name', 'controller', 'action'];

    /**
     * An array of all the registered routes.
     *
     * @var array
     */
    protected $routes = [];

    /**
     * Create a new route command instance.
     *
     * @param \Viserio\Component\Contract\Routing\Router $router
     */
    public function __construct(RouterContract $router)
    {
        parent::__construct();

        $this->routes = $router->getRoutes()->getRoutes();
    }

    /**
     * {@inheritdoc}
     */
    public function handle(): int
    {
        if (\count($this->routes) === 0) {
            $this->error("Your application doesn't have any routes.");

            return 1;
        }

        $this->table(self::$headers, $this->getRoutes());

        return 0;
    }

    /**
     * Compile the routes into a displayable format.
     *
     * @return array
     */
    protected function getRoutes(): array
    {
        $routes = [];

        foreach ($this->routes as $route) {
            if (($routeInfo = $this->getRouteInformation($route)) !== null) {
                $routes[] = $routeInfo;
            }
        }

        if ($sort = $this->option('sort')) {
            $routes = self::sort($routes, static function ($route) use ($sort) {
                return $route[$sort];
            });
        }

        if ($this->option('reverse')) {
            $routes = \array_reverse($routes);
        }

        return \array_filter($routes);
    }

    /**
     * Get the route information for a given route.
     *
     * @param \Viserio\Component\Contract\Routing\Route $route
     *
     * @return null|array
     */
    protected function getRouteInformation(RouteContract $route): ?array
    {
        $actions = \explode('@', $route->getActionName());

        return $this->filterRoute([
            'method'     => $route->getMethods(),
            'uri'        => $route->getUri(),
            'name'       => \is_string($route->getName()) ? "<fg=green>{$route->getName()}</>" : '-',
            'controller' => isset($actions[0]) ? "<fg=cyan>{$actions[0]}</>" : '-',
            'action'     => isset($actions[1]) ? "<fg=red>{$actions[1]}</>" : '-',
        ]);
    }

    /**
     * Filter the route by URI and / or name.
     *
     * @param array $route
     *
     * @return null|array
     */
    protected function filterRoute(array $route): ?array
    {
        $isNotName   = ($this->option('name') && \strpos($route['name'], $this->option('name')) === false);
        $isNotPath   = ($this->option('path') && \strpos($route['uri'], $this->option('path')) === false);
        $isNotMethod = ($this->option('method') && \in_array(\strtoupper($this->option('method')), $route['method'], true) === false);

        if ($isNotName || $isNotPath || $isNotMethod) {
            return null;
        }

        $route['method'] = \implode('|', $route['method']);

        return $route;
    }

    /**
     * Sort the array using the given callback.
     *
     * @param array    $array
     * @param callable $callback
     *
     * @return array
     */
    protected static function sort(array $array, callable $callback): array
    {
        $results = [];

        foreach ($array as $key => $value) {
            $results[$key] = $callback($value, $key);
        }

        \asort($results, \SORT_REGULAR);

        foreach (\array_keys($results) as $key) {
            $results[$key] = $array[$key];
        }

        return $results;
    }
}


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

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Source;
use Viserio\Bridge\Twig\Extension\TranslatorExtension;
use Viserio\Component\Translation\Extractor\AbstractFileExtractor;

class TwigExtractor extends AbstractFileExtractor
{
    /**
     * Default domain for found messages.
     *
     * @var string
     */
    protected $defaultDomain = 'messages';

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

    public function __construct(Environment $twig)
    {
        $this->twig = $twig;
    }

    /**
     * {@inheritdoc}
     */
    public function extract($resource): array
    {
        $messages = [];

        foreach ($this->extractFiles($resource) as $file) {
            try {
                $messages = \array_merge($messages, $this->extractTemplate(\file_get_contents($file)));
            } catch (Error $exception) {
                $exception->setSourceContext(new Source('', \basename($file), \realpath($file)));

                throw $exception;
            }
        }

        return $messages;
    }

    /**
     * @param string $template
     *
     * @throws \Twig_Error_Syntax
     *
     * @return array
     */
    protected function extractTemplate(string $template): array
    {
        /** @var \Viserio\Bridge\Twig\NodeVisitor\TranslationNodeVisitor $visitor */
        $visitor = $this->twig->getExtension(TranslatorExtension::class)->getTranslationNodeVisitor();
        $visitor->enable();

        $this->twig->parse($this->twig->tokenize(new Source($template, '')));

        $messages = [];

        foreach ($visitor->getMessages() as $message) {
            $messages[$message[1] ?? $this->defaultDomain][\trim($message[0])] = $this->prefix . \trim($message[0]);
        }

        $visitor->disable();

        return $messages;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeExtracted(string $file): bool
    {
        return $this->isFile($file) && $this->isTwigFile($file);
    }

    /**
     * @param array|string $directory
     *
     * @return array
     */
    protected function extractFromDirectory($directory): array
    {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
        );
        $files    = [];

        foreach ($iterator as $file) {
            if ($this->isTwigFile($file->getPathname())) {
                $files[] = $file->getPathname();
            }
        }

        return $files;
    }

    /**
     * Check if file is a php file.
     *
     * @param string $file
     *
     * @return bool
     */
    private function isTwigFile(string $file): bool
    {
        return \pathinfo($file, \PATHINFO_EXTENSION) === 'twig';
    }
}


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

use Psr\Http\Message\StreamInterface;
use Viserio\Component\Contract\Http\Exception\RuntimeException;

class LimitStream extends AbstractStreamDecorator
{
    /** @var \Psr\Http\Message\StreamInterface */
    protected $stream;

    /**
     * Offset to start reading from.
     *
     * @var int
     */
    private $offset;

    /**
     * Limit the number of bytes that can be read.
     *
     * @var int
     */
    private $limit;

    /**
     * Create a new limit stream instance.
     *
     * @param StreamInterface $stream Stream to wrap
     * @param int             $limit  Total number of bytes to allow to be read
     *                                from the stream. Pass -1 for no limit.
     * @param null|int        $offset position to seek to before reading (only
     *                                works on seekable streams)
     */
    public function __construct(StreamInterface $stream, int $limit = -1, ?int $offset = 0)
    {
        parent::__construct($stream);

        $this->setLimit($limit);
        $this->setOffset($offset);
    }

    /**
     * Set the offset to start limiting from.
     *
     * @param int $offset Offset to seek to and begin byte limiting from
     *
     * @throws \Viserio\Component\Contract\Http\Exception\RuntimeException if the stream cannot be seeked
     *
     * @return void
     */
    public function setOffset(int $offset): void
    {
        $current = $this->stream->tell();

        if ($current !== $offset) {
            // If the stream cannot seek to the offset position, then read to it
            if ($this->stream->isSeekable()) {
                $this->stream->seek($offset);
            } elseif ($current > $offset) {
                throw new RuntimeException(\sprintf('Could not seek to stream offset %s', $offset));
            } else {
                $this->stream->read($offset - $current);
            }
        }

        $this->offset = $offset;
    }

    /**
     * Set the limit of bytes that the decorator allows to be read from the
     * stream.
     *
     * @param int $limit number of bytes to allow to be read from the stream.
     *                   Use -1 for no limit
     *
     * @return void
     */
    public function setLimit(int $limit): void
    {
        $this->limit = $limit;
    }

    /**
     * {@inheritdoc}
     */
    public function eof(): bool
    {
        // Always return true if the underlying stream is EOF
        if ($this->stream->eof()) {
            return true;
        }

        // No limit and the underlying stream is not at EOF
        if ($this->limit === -1) {
            return false;
        }

        return $this->stream->tell() >= $this->offset + $this->limit;
    }

    /**
     * Returns the size of the limited subset of data.
     *
     * {@inheritdoc}
     */
    public function getSize(): ?int
    {
        if (($length = $this->stream->getSize()) === null) {
            return null;
        }

        if ($this->limit === -1) {
            return $length - $this->offset;
        }

        return \min($this->limit, $length - $this->offset);
    }

    /**
     * Allow for a bounded seek on the read limited stream.
     *
     * {@inheritdoc}
     */
    public function seek($offset, $whence = \SEEK_SET): void
    {
        if ($whence !== \SEEK_SET || $offset < 0) {
            throw new RuntimeException(\sprintf(
                'Cannot seek to offset %s with whence %s',
                $offset,
                $whence
            ));
        }

        $offset += $this->offset;

        if ($this->limit !== -1 && $offset > $this->offset + $this->limit) {
            $offset = $this->offset + $this->limit;
        }

        $this->stream->seek($offset);
    }

    /**
     * Give a relative tell().
     *
     * {@inheritdoc}
     */
    public function tell(): int
    {
        return $this->stream->tell() - $this->offset;
    }

    /**
     * {@inheritdoc}
     */
    public function read($length): string
    {
        if ($this->limit === -1) {
            return $this->stream->read($length);
        }

        // Check if the current position is less than the total allowed
        // bytes + original offset
        $remaining = ($this->offset + $this->limit) - $this->stream->tell();

        if ($remaining > 0) {
            // Only return the amount of requested data, ensuring that the byte
            // limit is not exceeded
            return $this->stream->read(\min($remaining, $length));
        }

        return '';
    }
}


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

use ArrayAccess;
use BadMethodCallException;
use Closure;
use Throwable;
use Viserio\Component\Contract\Support\Arrayable;
use Viserio\Component\Contract\Support\Renderable;
use Viserio\Component\Contract\View\Engine as EngineContract;
use Viserio\Component\Contract\View\Factory as FactoryContract;
use Viserio\Component\Contract\View\View as ViewContract;
use Viserio\Component\Support\Str;

class View implements ArrayAccess, ViewContract
{
    /**
     * The view factory instance.
     *
     * @var \Viserio\Component\Contract\View\Factory
     */
    protected $factory;

    /**
     * The engine implementation.
     *
     * @var \Viserio\Component\Contract\View\Engine
     */
    protected $engine;

    /**
     * The name of the view.
     *
     * @var string
     */
    protected $view;

    /**
     * The array of view data.
     *
     * @var array
     */
    protected $data = [];

    /**
     * The file info to the view file.
     *
     * @var array
     */
    protected $fileInfo;

    /**
     * Create a new view instance.
     *
     * @param \Viserio\Component\Contract\View\Factory            $factory
     * @param \Viserio\Component\Contract\View\Engine             $engine
     * @param string                                              $view
     * @param array                                               $fileInfo
     * @param array|\Viserio\Component\Contract\Support\Arrayable $data
     */
    public function __construct(
        FactoryContract $factory,
        EngineContract $engine,
        string $view,
        array $fileInfo,
        $data = []
    ) {
        $this->view     = $view;
        $this->fileInfo = $fileInfo;
        $this->engine   = $engine;
        $this->factory  = $factory;

        $this->data = $data instanceof Arrayable ? $data->toArray() : $data;
    }

    /**
     * Set a piece of data on the view.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function __set($key, $value): void
    {
        $this->with($key, $value);
    }

    /**
     * Check if a piece of data is bound to the view.
     *
     * @param string $key
     *
     * @return bool
     */
    public function __isset($key): bool
    {
        return isset($this->data[$key]);
    }

    /**
     * Remove a piece of bound data from the view.
     *
     * @param string $key
     *
     * @return void
     */
    public function __unset($key): void
    {
        unset($this->data[$key]);
    }

    /**
     * Dynamically bind parameters to the view.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @throws \BadMethodCallException
     *
     * @return \Viserio\Component\Contract\View\View
     */
    public function __call(string $method, array $parameters): ViewContract
    {
        if (Str::startsWith($method, 'with')) {
            return $this->with(Str::snake(\mb_substr($method, 4)), $parameters[0]);
        }

        throw new BadMethodCallException(\sprintf('Method [%s] does not exist on view.', $method));
    }

    /**
     * Get the string contents of the view.
     *
     * @return string
     */
    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 getFactory(): FactoryContract
    {
        return $this->factory;
    }

    /**
     * {@inheritdoc}
     */
    public function getEngine(): EngineContract
    {
        return $this->engine;
    }

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

    /**
     * {@inheritdoc}
     */
    public function render(callable $callback = null): string
    {
        try {
            $contents = $this->getContents();

            $response = isset($callback) ? $callback($this, $contents) : null;

            return $response ?? $contents;
        } catch (Throwable $exception) {
            throw $exception;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function with($key, $value = null): ViewContract
    {
        if (\is_array($key)) {
            $this->data = \array_merge($this->data, $key);
        } else {
            $this->data[$key] = $value;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function nest($key, string $view, array $data = []): ViewContract
    {
        return $this->with($key, $this->factory->create($view, $data));
    }

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

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

    /**
     * {@inheritdoc}
     */
    public function setPath(string $path): ViewContract
    {
        $this->fileInfo['path'] = $path;

        return $this;
    }

    /**
     * Get file extension.
     *
     * @return string
     */
    public function getExtension(): string
    {
        return $this->fileInfo['extension'];
    }

    /**
     * Determine if a piece of data is bound.
     *
     * @param string $key
     *
     * @return bool
     */
    public function offsetExists($key): bool
    {
        return \array_key_exists($key, $this->data);
    }

    /**
     * Get a piece of bound data to the view.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function offsetGet($key)
    {
        return $this->data[$key];
    }

    /**
     * Set a piece of data on the view.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function offsetSet($key, $value): void
    {
        $this->with($key, $value);
    }

    /**
     * Unset a piece of data from the view.
     *
     * @param string $key
     */
    public function offsetUnset($key): void
    {
        unset($this->data[$key]);
    }

    /**
     * Get a piece of data from the view.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function &__get($key)
    {
        return $this->data[$key];
    }

    /**
     * Get the evaluated contents of the view.
     *
     * @return string
     */
    protected function getContents(): string
    {
        return $this->engine->get($this->fileInfo, $this->gatherData());
    }

    /**
     * Get the data bound to the view instance.
     *
     * @return array
     */
    protected function gatherData(): array
    {
        $data = \array_merge($this->factory->getShared(), $this->data);

        foreach ($data as $key => $value) {
            if ($value instanceof Renderable) {
                $data[$key] = $value->render();
            } elseif ($value instanceof Closure) {
                $data[$key] = $value();
            }
        }

        return $data;
    }
}


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

use ReflectionClass;
use Symfony\Component\VarExporter\VarExporter;
use Viserio\Component\Console\Command\AbstractCommand;
use Viserio\Component\Parser\Dumper;

class OptionDumpCommand extends AbstractCommand
{
    /**
     * {@inheritdoc}
     */
    protected static $defaultName = 'option:dump';

    /**
     * {@inheritdoc}
     */
    protected $signature = 'option:dump
        [class : Name of the class to reflect.]
        [dir : Path to the config dir.]
        [--format=php : The output format (php, json, xml, json).]
        [--overwrite : Overwrite existent class config.]
        [--merge : Merge existent class config with a new class config.]
        [--show : You will see the config and be asked before the config is written to a file.]
    ';

    /**
     * {@inheritdoc}
     */
    protected $description = 'Dumps config files for found classes with RequiresConfig interface.';

    /**
     * {@inheritdoc}
     *
     * @throws \Viserio\Component\Contract\OptionsResolver\Exception\InvalidArgumentException if dir cant be created or is not writable
     */
    public function handle(): int
    {
        $format = $this->option('format');
        $dumper = null;

        if ($this->container !== null && $this->getContainer()->has(Dumper::class)) {
            $dumper = $this->getContainer()->get(Dumper::class);
        }

        if ($dumper === null && $format !== 'php') {
            $this->error('Only the php format is supported; use composer req viserio/parser to get [json], [xml], [yml] output.');

            return 1;
        }

        $dirPath = $this->argument('dir');

        if (! \is_dir($dirPath) && ! @\mkdir($dirPath, 0777, true)) {
            throw new \RuntimeException(\sprintf('Config directory [%s] cannot be created or is write protected.', $dirPath));
        }

        $className = $this->argument('class');
        $configs   = $this->getConfigReader()->readConfig(new ReflectionClass($className));

        foreach ($configs as $key => $config) {
            $file = $dirPath . \DIRECTORY_SEPARATOR . $key . '.' . $format;

            if ($this->hasOption('merge') && \file_exists($file)) {
                $existingConfig = includeFile($file);
                $config         = \array_replace_recursive($existingConfig, $config);
            }

            if ($dumper !== null && $format !== 'php') {
                $content = $dumper->dump($config, $format) . \PHP_EOL;
            } else {
                $content = '<?php' . \PHP_EOL . 'declare(strict_types=1);' . \PHP_EOL . \PHP_EOL . 'return ';
                $content .= VarExporter::export($config) . ';' . \PHP_EOL;
            }

            if ($this->hasOption('show')) {
                $this->info('Output array:' . \PHP_EOL . \PHP_EOL . $content);

                if ($this->confirm(\sprintf('Write content to [%s]?', $file)) === false) {
                    continue;
                }
            }

            $this->info(\sprintf('Dumping [%s] configuration to [%s].', $className, $file));
            $this->putContentToFile($file, $content, $key);
        }

        return 0;
    }

    /**
     * Put the created content to file.
     *
     * @param string $file
     * @param string $content
     * @param string $key
     *
     * @return void
     */
    private function putContentToFile(string $file, string $content, string $key): void
    {
        if ($this->hasOption('overwrite') || ! \file_exists($file)) {
            \file_put_contents($file, $content);
        } else {
            if ($this->hasOption('merge')) {
                $confirmed = true;
            } else {
                $confirmed = $this->confirm(\sprintf('Do you really wish to overwrite %s', $key));
            }

            if ($confirmed) {
                \file_put_contents($file, $content);
            }
        }
    }

    /**
     * Get a modified OptionsReader instance.
     *
     * @return \Viserio\Component\OptionsResolver\Command\OptionsReader
     */
    private function getConfigReader(): OptionsReader
    {
        $command = $this;

        return new class($command) extends OptionsReader {
            /**
             * A OptionDumpCommand instance.
             *
             * @var \Viserio\Component\OptionsResolver\Command\OptionDumpCommand
             */
            private $command;

            /**
             * @param \Viserio\Component\OptionsResolver\Command\OptionDumpCommand $command
             */
            public function __construct(OptionDumpCommand $command)
            {
                $this->command = $command;
            }

            /**
             * Read the mandatory options and ask for the value.
             *
             * @param string $className
             * @param array  $dimensions
             * @param array  $mandatoryOptions
             *
             * @return array
             */
            protected function readMandatoryOption(string $className, array $dimensions, array $mandatoryOptions): array
            {
                $options = [];

                foreach ($mandatoryOptions as $key => $mandatoryOption) {
                    if (! \is_scalar($mandatoryOption)) {
                        $options[$key] = $this->readMandatoryOption($className, $dimensions, $mandatoryOptions[$key]);

                        continue;
                    }

                    $options[$mandatoryOption] = $this->command->ask(
                        \sprintf(
                            '%s: Please enter the following mandatory value for [%s]',
                            $className,
                            \implode('.', $dimensions) . '.' . $mandatoryOption
                        )
                    );
                }

                return $options;
            }
        };
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 *
 * @param string $file
 *
 * @return array
 */
function includeFile(string $file): array
{
    return (array) include $file;
}


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

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Routing\MiddlewareAware as MiddlewareAwareContract;
use Viserio\Component\Contract\Routing\Route as RouteContract;
use Viserio\Component\Routing\MiddlewareNameResolver;
use Viserio\Component\Routing\Pipeline;
use Viserio\Component\Routing\SortedMiddleware;
use Viserio\Component\Routing\Traits\MiddlewareAwareTrait;

class MiddlewareBasedDispatcher extends SimpleDispatcher implements MiddlewareAwareContract
{
    use ContainerAwareTrait;
    use MiddlewareAwareTrait;

    /**
     * All of the middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [];

    /**
     * The priority-sorted list of middleware.
     *
     * Forces the listed middleware to always be in the given order.
     *
     * @var array
     */
    protected $middlewarePriority = [];

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function withoutMiddleware($middleware = null): MiddlewareAwareContract
    {
        // not used!

        return $this;
    }

    /**
     * Register a group of middleware.
     *
     * @param string $name
     * @param array  $middleware
     *
     * @return void
     */
    public function setMiddlewareGroup(string $name, array $middleware): void
    {
        $this->middlewareGroups[$name] = $middleware;
    }

    /**
     * Set a list of middleware priorities.
     *
     * @param array $middlewarePriorities
     *
     * @return void
     */
    public function setMiddlewarePriorities(array $middlewarePriorities): void
    {
        $this->middlewarePriority = $middlewarePriorities;
    }

    /**
     * Get a list of middleware priorities.
     *
     * @return array
     */
    public function getMiddlewarePriorities(): array
    {
        return $this->middlewarePriority;
    }

    /**
     * Get a middleware list.
     *
     * @return array
     */
    public function getMiddleware(): array
    {
        return $this->middleware;
    }

    /**
     * Run the given route within a Stack "onion" instance.
     *
     * @param \Viserio\Component\Contract\Routing\Route $route
     * @param \Psr\Http\Message\ServerRequestInterface  $request
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function runRoute(RouteContract $route, ServerRequestInterface $request): ResponseInterface
    {
        $pipeline = new Pipeline();

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

        return $pipeline->send($request)
            ->through($this->gatherRouteMiddleware($route))
            ->then(static function ($request) use ($route) {
                return $route->run($request);
            });
    }

    /**
     * Gather the middleware for the given route.
     *
     * @param \Viserio\Component\Contract\Routing\Route $route
     *
     * @return array
     */
    protected function gatherRouteMiddleware(RouteContract $route): array
    {
        $middleware = [];

        self::map($route->gatherMiddleware(), function ($nameOrObject) use (&$middleware, $route): void {
            $bypass = $route->gatherDisabledMiddleware();

            if (\is_object($nameOrObject) && ! isset($bypass[\get_class($nameOrObject)])) {
                $middleware[] = $nameOrObject;
            } else {
                $middleware[] = MiddlewareNameResolver::resolve(
                    $nameOrObject,
                    $this->middleware,
                    $this->middlewareGroups,
                    $bypass
                );
            }
        });

        return (new SortedMiddleware(
            $this->middlewarePriority,
            self::flatten($middleware)
        ))->getAll();
    }

    /**
     * Applies the callback to the elements of the given arrays.
     *
     * @param array    $array
     * @param callable $callback
     *
     * @return array
     */
    protected static function map(array $array, callable $callback): array
    {
        $newArray = [];

        foreach ($array as $key => $item) {
            $result = $callback($item, $key);

            $newArray = \array_merge_recursive($array, (array) $result);
        }

        return $newArray;
    }

    /**
     * Convert a multi-dimensional array into a single-dimensional array without keys.
     *
     * @param array $array
     *
     * @return array
     */
    protected static function flatten(array $array): array
    {
        $flattened = [];

        foreach ($array as $key => $value) {
            if (\is_array($value)) {
                $flattened = \array_merge($flattened, static::flatten($value));
            } else {
                $flattened[] = $value;
            }
        }

        return $flattened;
    }
}


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

use Viserio\Component\Config\Provider\ConfigServiceProvider;
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\Bootstrap\AbstractLoadFiles;
use Viserio\Component\Foundation\Bootstrap\LoadServiceProvider;

class LoadConfiguration extends AbstractLoadFiles implements BootstrapStateContract
{
    /**
     * Supported config files.
     *
     * @var string[]
     */
    protected const CONFIG_EXTS = [
        'php',
        'xml',
        'yaml',
        'yml',
        'toml',
    ];

    /**
     * {@inheritdoc}
     */
    protected static $bypassFiles = [
        'serviceproviders',
        'staticalproxy',
        'bootstrap',
    ];

    /**
     * {@inheritdoc}
     */
    public static function getPriority(): int
    {
        return 32;
    }

    /**
     * {@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();
        $container->register(new ConfigServiceProvider());

        $config = $container->get(RepositoryContract::class);

        $loadedFromCache = false;

        // First we will see if we have a cache configuration file.
        // If we do, we'll load the configuration items.
        if (\file_exists($cached = $kernel->getStoragePath('framework' . \DIRECTORY_SEPARATOR . 'config.cache.php'))) {
            $items = require $cached;

            $config->setArray($items, true);

            $loadedFromCache = true;
        }

        // Next we will spin through all of the configuration files in the configuration
        // directory and load each one into the config manager.
        if (! $loadedFromCache) {
            $config->set('viserio.app.env', $kernel->getEnvironment());

            static::loadConfigurationFiles($kernel, $config);
        }
    }

    /**
     * Load the configuration items from all of the files.
     *
     * @param \Viserio\Component\Contract\Foundation\Kernel $kernel
     * @param \Viserio\Component\Contract\Config\Repository $config
     *
     * @return void
     */
    protected static function loadConfigurationFiles(KernelContract $kernel, RepositoryContract $config): void
    {
        foreach (static::getFiles($kernel->getConfigPath('packages'), self::CONFIG_EXTS) as $path) {
            $config->import($path);
        }

        foreach (static::getFiles($kernel->getConfigPath(), self::CONFIG_EXTS) as $path) {
            $config->import($path);
        }

        foreach (static::getFiles($kernel->getConfigPath($kernel->getEnvironment()), self::CONFIG_EXTS) as $path) {
            $config->import($path);
        }

        foreach (static::getFiles($kernel->getConfigPath('packages' . \DIRECTORY_SEPARATOR . $kernel->getEnvironment()), self::CONFIG_EXTS) as $path) {
            $config->import($path);
        }
    }
}


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

use Closure;
use ReflectionClass;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Pipeline\Exception\RuntimeException;
use Viserio\Component\Contract\Pipeline\Pipeline as PipelineContract;
use Viserio\Component\Support\Traits\InvokerAwareTrait;

class Pipeline implements PipelineContract
{
    use ContainerAwareTrait;
    use InvokerAwareTrait;

    /**
     * The object being passed through the pipeline.
     *
     * @var mixed
     */
    protected $traveler;

    /**
     * The method to call on each stage.
     *
     * @var string
     */
    protected $method = 'handle';

    /**
     * The array of class pipes.
     *
     * @var array
     */
    protected $stages = [];

    /**
     * {@inheritdoc}
     */
    public function send($traveler): PipelineContract
    {
        $this->traveler = $traveler;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function through($stages): PipelineContract
    {
        $this->stages = \is_array($stages) ? $stages : \func_get_args();

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function via(string $method): PipelineContract
    {
        $this->method = $method;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function then(Closure $destination)
    {
        $firstSlice = $this->getInitialSlice($destination);

        $stages = \array_reverse($this->stages);

        $callable = \array_reduce($stages, $this->getSlice(), $firstSlice);

        return $callable($this->traveler);
    }

    /**
     * Get a Closure that represents a slice of the application onion.
     *
     * @return \Closure
     */
    protected function getSlice(): Closure
    {
        return function ($stack, $stage) {
            return function ($traveler) use ($stack, $stage) {
                // If the $stage is an instance of a Closure, we will just call it directly.
                if ($stage instanceof Closure) {
                    return $stage($traveler, $stack);
                }

                // Otherwise we'll resolve the stages out of the container and call it with
                // the appropriate method and arguments, returning the results back out.
                if ($this->container && ! \is_object($stage) && \is_string($stage)) {
                    return $this->sliceThroughContainer($traveler, $stack, $stage);
                }

                if (\is_array($stage)) {
                    $parameters = [$traveler, $stack];
                    $class      = \array_shift($stage);

                    if (\is_object($class) && (\is_string($class) && \class_exists($class))) {
                        throw new \InvalidArgumentException(\sprintf(
                            'The first entry in the array must be a class, [%s] given.',
                            \is_object($class) ? \get_class($class) : \gettype($class)
                        ));
                    }

                    /** @var \Closure $object */
                    $object = (new ReflectionClass($class))->newInstanceArgs($stage);

                    return $object(...$parameters);
                }

                // If the pipe is already an object we'll just make a callable and pass it to
                // the pipe as-is. There is no need to do any extra parsing and formatting
                // since the object we're given was already a fully instantiated object.
                $parameters = [$traveler, $stack];

                return $stage->{$this->method}(...$parameters);
            };
        };
    }

    /**
     * Get the initial slice to begin the stack call.
     *
     * @param \Closure $destination
     *
     * @return \Closure
     */
    protected function getInitialSlice(Closure $destination): Closure
    {
        return static function ($traveler) use ($destination) {
            return $destination($traveler);
        };
    }

    /**
     * Parse full pipe string to get name and parameters.
     *
     * @param string $stage
     *
     * @return array
     */
    protected function parseStageString(string $stage): array
    {
        [$name, $parameters] = \array_pad(\explode(':', $stage, 2), 2, []);

        if (\is_string($parameters)) {
            $parameters = \explode(',', $parameters);
        }

        return [$name, $parameters];
    }

    /**
     * Resolve from container.
     *
     * @param mixed  $traveler
     * @param mixed  $stack
     * @param string $stage
     *
     * @throws \Viserio\Component\Contract\Pipeline\Exception\RuntimeException
     *
     * @return mixed
     */
    protected function sliceThroughContainer($traveler, $stack, string $stage)
    {
        [$name, $parameters] = $this->parseStageString($stage);
        $parameters          = \array_merge([$traveler, $stack], $parameters);

        $class = null;

        if ($this->container->has($name)) {
            $class = $this->container->get($name);
        } else {
            throw new RuntimeException(\sprintf('Class [%s] is not being managed by the container.', $name));
        }

        return $this->getInvoker()->call([$class, $this->method], $parameters);
    }
}


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

use Closure;
use ErrorException;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application as SymfonyConsole;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Process\PhpExecutableFinder;
use Throwable;
use Viserio\Component\Console\Command\AbstractCommand as ViserioCommand;
use Viserio\Component\Console\Command\CommandResolver;
use Viserio\Component\Console\Command\ListCommand;
use Viserio\Component\Console\Command\StringCommand;
use Viserio\Component\Console\Event\ConsoleCommandEvent;
use Viserio\Component\Console\Event\ConsoleErrorEvent;
use Viserio\Component\Console\Event\ConsoleTerminateEvent;
use Viserio\Component\Console\Input\InputOption;
use Viserio\Component\Contract\Console\Exception\LogicException;
use Viserio\Component\Contract\Container\Traits\ContainerAwareTrait;
use Viserio\Component\Contract\Events\Traits\EventManagerAwareTrait;
use Viserio\Component\Support\Traits\InvokerAwareTrait;

class Application extends SymfonyConsole
{
    use ContainerAwareTrait;
    use EventManagerAwareTrait;
    use InvokerAwareTrait;

    /**
     * The console application bootstrappers.
     *
     * @var array
     */
    protected static $bootstrappers = [];

    /**
     * Console name.
     *
     * @var string
     */
    protected $name;

    /**
     * Console version.
     *
     * @var string
     */
    protected $version;

    /**
     * The running command.
     *
     * @var null|\Symfony\Component\Console\Command\Command
     */
    private $runningCommand;

    /**
     * The output from the previous command.
     *
     * @var \Symfony\Component\Console\Output\OutputInterface
     */
    private $lastOutput;

    /**
     * Symfony terminal instance.
     *
     * @var \Symfony\Component\Console\Terminal
     */
    private $terminal;

    /**
     * Create a new cerebro console application.
     *
     * @param string $version The version of the application
     * @param string $name    The name of the application
     */
    public function __construct(string $version = 'UNKNOWN', string $name = 'UNKNOWN')
    {
        $this->name     = $name;
        $this->version  = $version;
        $this->terminal = new Terminal();

        $this->setAutoExit(false);
        $this->setCatchExceptions(false);

        parent::__construct($name, $version);

        $this->bootstrap();
    }

    /**
     * Get the output for the last run command.
     *
     * @return string
     */
    public function getLastOutput(): string
    {
        if (\method_exists($this->lastOutput, 'fetch')) {
            return $this->lastOutput->fetch();
        }

        return '';
    }

    /**
     * Add a command to the console.
     *
     * @param \Symfony\Component\Console\Command\Command $command
     *
     * @return null|\Symfony\Component\Console\Command\Command|\Viserio\Component\Console\Command\AbstractCommand
     */
    public function add(SymfonyCommand $command): ?SymfonyCommand
    {
        if ($command instanceof ViserioCommand) {
            if ($this->container !== null) {
                $command->setContainer($this->container);
            }

            $command->setInvoker($this->getInvoker());
        }

        return parent::add($command);
    }

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

        $this->add($command);

        return $command;
    }

    /**
     * Run an console command by name.
     *
     * @param string                                                 $command
     * @param array                                                  $parameters
     * @param null|\Symfony\Component\Console\Output\OutputInterface $outputBuffer
     *
     * @throws \Throwable
     *
     * @return int
     */
    public function call(string $command, array $parameters = [], ?OutputInterface $outputBuffer = null): int
    {
        if (\is_subclass_of($command, SymfonyCommand::class)) {
            /** @var \Symfony\Component\Console\Command\Command $symfonyCommand */
            $symfonyCommand = $command;

            if (($commandName = $symfonyCommand::getDefaultName()) !== null) {
                $command = $commandName;
            }
        }

        if (! $this->has($command)) {
            throw new CommandNotFoundException(\sprintf('The command [%s] does not exist.', $command));
        }

        $this->lastOutput = $outputBuffer ?: new BufferedOutput();

        $this->setCatchExceptions(false);

        \array_unshift($parameters, $command);

        $input = new ArrayInput($parameters);

        if ($input->hasParameterOption(['--no-interaction'], true)) {
            $input->setInteractive(false);
        }

        $result = $this->run($input, $this->lastOutput);

        $this->setCatchExceptions(true);

        return $result;
    }

    /**
     * Define default values for the arguments of the command.
     *
     * @param string $commandName      name of the command
     * @param array  $argumentDefaults default argument values
     *
     * @return void
     */
    public function defaults(string $commandName, array $argumentDefaults = []): void
    {
        $command           = $this->get($commandName);
        $commandDefinition = $command->getDefinition();

        foreach ($argumentDefaults as $name => $default) {
            $argument = $commandDefinition->getArgument($name);
            $argument->setDefault($default);
        }
    }

    /**
     * Register an application starting bootstrapper.
     *
     * @param \Closure $callback
     *
     * @return void
     */
    public static function starting(Closure $callback): void
    {
        static::$bootstrappers[] = $callback;
    }

    /**
     * Clear the console application bootstrappers.
     *
     * @return void
     */
    public static function clearBootstrappers(): void
    {
        static::$bootstrappers = [];
    }

    /**
     * The PHP executable.
     *
     * @return string
     */
    public static function phpBinary(): string
    {
        $finder = (new PhpExecutableFinder())->find(false);

        return \escapeshellarg($finder === false ? '' : $finder);
    }

    /**
     * The Cerebro executable.
     *
     * @return string
     */
    public static function cerebroBinary(): string
    {
        $constant = \defined('CEREBRO_BINARY') ? \constant('CEREBRO_BINARY') : null;

        return  $constant !== null ? \escapeshellarg($constant) : 'cerebro';
    }

    /**
     * Format the given command as a fully-qualified executable command.
     *
     * @param string $string
     *
     * @return string
     */
    public static function formatCommandString(string $string): string
    {
        return \sprintf('%s %s %s', static::phpBinary(), static::cerebroBinary(), $string);
    }

    /**
     * Get the container instance.
     *
     * @throws \Viserio\Component\Contract\Console\Exception\LogicException
     *
     * @return \Psr\Container\ContainerInterface
     */
    public function getContainer(): ContainerInterface
    {
        if (! $this->container) {
            throw new LogicException('Container is not set up.');
        }

        return $this->container;
    }

    /**
     * {@inheritdoc}
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        \putenv('LINES=' . $this->terminal->getHeight());
        \putenv('COLUMNS=' . $this->terminal->getWidth());

        if ($input === null) {
            $input = new ArgvInput();
        }

        if ($output === null) {
            $output = new ConsoleOutput();
        }

        $renderException = function (Throwable $e) use ($output): void {
            if (! $e instanceof Exception) {
                $e = new ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
            }

            if ($output instanceof ConsoleOutputInterface) {
                $this->renderException($e, $output->getErrorOutput());
            } else {
                $this->renderException($e, $output);
            }
        };

        $debugHandler = false;

        /** @var (callable|array<object,string>)[] $phpHandler */
        $phpHandler = \set_exception_handler($renderException);

        if (\is_callable($phpHandler)) {
            \restore_exception_handler();

            if (! \is_array($phpHandler) || ! $phpHandler[0] instanceof ErrorHandler) {
                $debugHandler = true;
            } elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($debugHandler);
            }
        }

        $this->configureIO($input, $output);

        try {
            $exitCode = $this->doRun($input, $output);
        } catch (Throwable $exception) {
            $exitCode = $exception->getCode();

            if ($this->eventManager !== null) {
                $this->eventManager->trigger($event = new ConsoleErrorEvent($this->runningCommand, $input, $output, $exception));

                $exitCode = $event->getExitCode();

                $this->eventManager->trigger(new ConsoleTerminateEvent($this->runningCommand, $input, $output, $exitCode));

                if ($exitCode === 0) {
                    return 0;
                }

                $exception = $event->getError();
            }

            if (! $this->areExceptionsCaught() || ! $exception instanceof Exception) {
                throw $exception;
            }

            $renderException($exception);

            if (\is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;

                if ($exitCode === 0) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        } finally {
            // if the exception handler changed, keep it
            // otherwise, unregister $renderException
            if (! $phpHandler) {
                if (\set_exception_handler($renderException) === $renderException) {
                    \restore_exception_handler();
                }

                \restore_exception_handler();
            } elseif (! $debugHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);

                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }

            $this->runningCommand = null;
        }

        if ($this->isAutoExitEnabled()) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @param \Symfony\Component\Console\Command\Command        $command
     * @param \Symfony\Component\Console\Input\InputInterface   $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @throws \Throwable
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(SymfonyCommand $command, InputInterface $input, OutputInterface $output): int
    {
        $this->runningCommand = $command;

        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        if ($this->eventManager === null) {
            return $command->run($input, $output);
        }

        // bind before the console.command event, so the listeners have access to input options/arguments
        try {
            $command->mergeApplicationDefinition();
            $input->bind($command->getDefinition());
        } catch (ExceptionInterface $exception) {
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
        }

        $event     = new ConsoleCommandEvent($command, $input, $output);
        $exception = null;

        try {
            $this->eventManager->trigger($event);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (Throwable $exception) {
            $this->eventManager->trigger($event = new ConsoleErrorEvent($command, $input, $output, $exception));
            $exception = $event->getError();

            if (($exitCode = $event->getExitCode()) === 0) {
                $exception = null;
            }
        }

        $this->eventManager->trigger($event = new ConsoleTerminateEvent($command, $input, $output, $exitCode));

        if ($exception !== null) {
            throw $exception;
        }

        return $event->getExitCode();
    }

    /**
     * Get the default input definitions for the applications.
     *
     * This is used to add the --env option to every available command.
     *
     * @return \Symfony\Component\Console\Input\InputDefinition
     */
    protected function getDefaultInputDefinition(): InputDefinition
    {
        $definition = parent::getDefaultInputDefinition();
        $definition->addOption($this->getEnvironmentOption());

        return $definition;
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return \Symfony\Component\Console\Command\Command[]|\Viserio\Component\Console\Command\AbstractCommand[]
     */
    protected function getDefaultCommands(): array
    {
        $helpCommand = new HelpCommand();
        $helpCommand->setHidden(true);

        return [$helpCommand, new ListCommand()];
    }

    /**
     * Get the global environment option for the definition.
     *
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     *
     * @return \Viserio\Component\Console\Input\InputOption
     */
    private function getEnvironmentOption(): InputOption
    {
        $message = 'The environment the command should run under.';

        return new InputOption('--env', null, InputOption::VALUE_OPTIONAL, $message);
    }

    /**
     * Bootstrap the console application.
     *
     * @return void
     */
    private function bootstrap(): void
    {
        foreach (static::$bootstrappers as $bootstrapper) {
            $bootstrapper($this);
        }
    }
}


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

use Viserio\Component\Contract\Routing\Route as RouteContract;
use Viserio\Component\Contract\Routing\SegmentMatcher as SegmentMatcherContract;
use Viserio\Component\Routing\Matcher\ParameterMatcher;

final class RouteTreeBuilder
{
    /**
     * Creates a route tree from the supplied routes.
     *
     * @param \Viserio\Component\Contract\Routing\Route[] $routes
     *
     * @return array
     */
    public function build(array $routes): array
    {
        $rootRouteData = null;
        $nodes         = [];
        $groupedRoutes = [];

        foreach ($routes as $route) {
            $groupedRoutes[\count($route->getSegments())][] = $route;
        }

        if (isset($groupedRoutes[0])) {
            $rootRouteData = new MatchedRouteDataMap();
            $rootRouteData->addRoute($groupedRoutes[0][0], []);

            unset($groupedRoutes[0]);
        }

        foreach ($groupedRoutes as $segmentDepth => $group) {
            $groupNodes = [];

            foreach ($group as $route) {
                $parameterIndexNameMap = [];
                $segments              = $route->getSegments();
                $segmentMatcher        = $this->getMatcher(\array_shift($segments), $parameterIndexNameMap);
                $firstSegmentHash      = $segmentMatcher->getHash();

                if (! isset($groupNodes[$firstSegmentHash])) {
                    $groupNodes[$firstSegmentHash] = new RouteTreeNode(
                        [0 => $segmentMatcher],
                        $segmentDepth === 1 ? new MatchedRouteDataMap() : new ChildrenNodeCollection()
                    );
                }

                $this->addRouteToNode($groupNodes[$firstSegmentHash], $route, $segments, 1, $parameterIndexNameMap);
            }

            $nodes[$segmentDepth] = new ChildrenNodeCollection($groupNodes);
        }

        return [$rootRouteData, $nodes];
    }

    /**
     * Adds a route to the node tree.
     *
     * @param \Viserio\Component\Routing\TreeGenerator\RouteTreeNode $node
     * @param \Viserio\Component\Contract\Routing\Route              $route
     * @param array                                                  $segments
     * @param int                                                    $segmentDepth
     * @param array                                                  $parameterIndexNameMap
     *
     * @throws \Viserio\Component\Contract\Routing\Exception\RuntimeException
     *
     * @return void
     */
    private function addRouteToNode(
        RouteTreeNode $node,
        RouteContract $route,
        array $segments,
        int $segmentDepth,
        array $parameterIndexNameMap
    ): void {
        if (\count($segments) === 0) {
            $node->getContents()->addRoute($route, $parameterIndexNameMap);

            return;
        }

        $childSegmentMatcher = $this->getMatcher(\array_shift($segments), $parameterIndexNameMap);

        if ($node->getContents()->hasChildFor($childSegmentMatcher)) {
            $child = $node->getContents()->getChild($childSegmentMatcher);
        } else {
            $child = new RouteTreeNode(
                [$segmentDepth => $childSegmentMatcher],
                \count($segments) === 0 ? new MatchedRouteDataMap() : new ChildrenNodeCollection()
            );
            $node->getContents()->addChild($child);
        }

        $this->addRouteToNode($child, $route, $segments, $segmentDepth + 1, $parameterIndexNameMap);
    }

    /**
     * Get the right Matcher.
     *
     * @param \Viserio\Component\Contract\Routing\SegmentMatcher|\Viserio\Component\Routing\Matcher\ParameterMatcher $firstSegment
     * @param array                                                                                $parameterIndexNameMap
     *
     * @return \Viserio\Component\Contract\Routing\SegmentMatcher
     */
    private function getMatcher($firstSegment, array &$parameterIndexNameMap): SegmentMatcherContract
    {
        if ($firstSegment instanceof ParameterMatcher) {
            return $firstSegment->getMatcher($parameterIndexNameMap);
        }

        return $firstSegment;
    }
}


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

use Interop\Container\ServiceProviderInterface;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemInterface;
use Psr\Container\ContainerInterface;
use Viserio\Component\Contract\Cache\Manager as CacheManagerContract;
use Viserio\Component\Filesystem\Cache\CachedFactory;
use Viserio\Component\Filesystem\FilesystemManager;

class FilesystemServiceProvider implements ServiceProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFactories(): array
    {
        return [
            FilesystemManager::class => [self::class, 'createFilesystemManager'],
            'flysystem'              => static function (ContainerInterface $container) {
                return $container->get(FilesystemManager::class);
            },
            'flysystem.connection' => [self::class, 'createFlysystemConnection'],
            Filesystem::class      => static function (ContainerInterface $container) {
                return $container->get(FilesystemManager::class);
            },
            FilesystemInterface::class => static function (ContainerInterface $container) {
                return $container->get(FilesystemManager::class);
            },
            CachedFactory::class      => [self::class, 'createCachedFactory'],
            'flysystem.cachedfactory' => static function (ContainerInterface $container) {
                return $container->get(CachedFactory::class);
            },
        ];
    }

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

    /**
     * Create a new FilesystemManager instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Filesystem\FilesystemManager
     */
    public static function createFilesystemManager(ContainerInterface $container): FilesystemManager
    {
        $manager = new FilesystemManager($container->get('config'));

        if ($container->has(CacheManagerContract::class)) {
            $manager->setCacheManager($container->get(CacheManagerContract::class));
        }

        return $manager;
    }

    /**
     * Create a new Connector instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Contract\Filesystem\Connector|\Viserio\Component\Filesystem\FilesystemAdapter
     */
    public static function createFlysystemConnection(ContainerInterface $container)
    {
        return $container->get(FilesystemManager::class)->getConnection();
    }

    /**
     * Create a new CachedFactory instance.
     *
     * @param \Psr\Container\ContainerInterface $container
     *
     * @return \Viserio\Component\Filesystem\Cache\CachedFactory
     */
    public static function createCachedFactory(ContainerInterface $container): CachedFactory
    {
        $cache = null;

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

        return new CachedFactory(
            $container->get(FilesystemManager::class),
            $cache
        );
    }
}


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

use PDO;
use PDOException;

class TraceablePDODecorater extends PDO
{
    /**
     * PDO instance.
     *
     * @var \PDO
     */
    protected $pdo;

    /**
     * All executed statements.
     *
     * @var array
     */
    protected $executedStatements = [];

    /**
     * Create a new TraceablePDODecorater instance.
     *
     * @param \PDO $pdo
     */
    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
        $this->pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, [TraceablePDOStatementDecorater::class, [$this]]);
    }

    /**
     * Returns the list of executed statements as TracedStatement objects.
     *
     * @return array
     */
    public function getExecutedStatements(): array
    {
        return $this->executedStatements;
    }

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

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

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

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

    /**
     * {@inheritdoc}
     *
     * @param mixed $statement
     */
    public function exec($statement)
    {
        return $this->profileCall('exec', $statement, \func_get_args());
    }

    /**
     * {@inheritdoc}
     */
    public function getAttribute($attribute)
    {
        return $this->pdo->getAttribute($attribute);
    }

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

    /**
     * {@inheritdoc}
     */
    public function lastInsertId($name = null): string
    {
        return $this->pdo->lastInsertId($name);
    }

    /**
     * {@inheritdoc}
     */
    public function prepare($statement, $driver_options = []): \PDOStatement
    {
        return $this->pdo->prepare($statement, $driver_options);
    }

    /**
     * {@inheritdoc}
     */
    public function query($statement)
    {
        return $this->profileCall('query', $statement, \func_get_args());
    }

    /**
     * {@inheritdoc}
     */
    public function quote($string, $parameter_type = PDO::PARAM_STR): string
    {
        return $this->pdo->quote($string, $parameter_type);
    }

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

    /**
     * {@inheritdoc}
     */
    public function setAttribute($attribute, $value): bool
    {
        return $this->pdo->setAttribute($attribute, $value);
    }

    /**
     * Adds an executed TracedStatement.
     *
     * @param \Viserio\Component\Profiler\DataCollector\Bridge\PDO\TracedStatement $stmt
     */
    public function addExecutedStatement(TracedStatement $stmt): void
    {
        $this->executedStatements[] = $stmt;
    }

    /**
     * Returns the accumulated execution time of statements.
     *
     * @return int
     */
    public function getAccumulatedStatementsDuration(): int
    {
        return \array_reduce($this->executedStatements, static function ($v, $s) {
            return $v + $s->getDuration();
        });
    }

    /**
     * Returns the peak memory usage while performing statements.
     *
     * @return int
     */
    public function getMemoryUsage(): int
    {
        return \array_reduce($this->executedStatements, static function ($v, $s) {
            return $v + $s->getMemoryUsage();
        });
    }

    /**
     * Returns the peak memory usage while performing statements.
     *
     * @return int
     */
    public function getPeakMemoryUsage(): int
    {
        return \array_reduce($this->executedStatements, static function ($v, $s) {
            $m = $s->getEndMemory();

            return $m > $v ? $m : $v;
        });
    }

    /**
     * Returns the list of failed statements.
     *
     * @return array
     */
    public function getFailedExecutedStatements(): array
    {
        return \array_filter($this->executedStatements, static function ($s) {
            return ! $s->isSuccess();
        });
    }

    /**
     * Profiles a call to a PDO method.
     *
     * @param string $method
     * @param string $sql
     * @param array  $args
     *
     * @return mixed
     */
    protected function profileCall(string $method, string $sql, array $args)
    {
        $trace = new TracedStatement($sql);
        $trace->start();

        $ex     = null;
        $result = null;

        try {
            $result = $this->pdo->{$method}(...$args);
        } catch (PDOException $e) {
            $ex = $e;
        }

        if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION && $result === false) {
            $error = $this->pdo->errorInfo();
            $ex    = new PDOException($error[2], $error[0]);
        }

        $trace->end($ex);

        $this->addExecutedStatement($trace);

        if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION && $ex !== null) {
            throw $ex;
        }

        return $result;
    }
}


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

use Psr\Http\Message\StreamInterface;
use Viserio\Component\Contract\Http\Exception\InvalidArgumentException;
use Viserio\Component\Http\Response;
use Viserio\Component\Http\Response\Traits\InjectContentTypeTrait;
use Viserio\Component\Http\Stream;

class HtmlResponse extends Response
{
    use InjectContentTypeTrait;

    /**
     * Create an HTML response.
     *
     * Produces an HTML response with a Content-Type of text/html and a default
     * status of 200.
     *
     * @param \Psr\Http\Message\StreamInterface|string $html    HTML or stream for the message body
     * @param null|string                              $charset content charset; default is utf-8
     * @param int                                      $status  integer status code for the response; 200 by default
     * @param array                                    $headers array of headers to use at initialization
     * @param string                                   $version protocol version
     *
     * @throws \Narrowspark\HttpStatus\Exception\InvalidArgumentException
     * @throws \Viserio\Component\Contract\Http\Exception\InvalidArgumentException if $html is neither a string or stream
     */
    public function __construct(
        $html,
        ?string $charset = null,
        int $status      = self::STATUS_OK,
        array $headers   = [],
        string $version  = '1.1'
    ) {
        parent::__construct(
            $status,
            $this->injectContentType('text/html; charset=' . ($charset ?? 'utf-8'), $headers),
            $this->createBody($html),
            $version
        );
    }

    /**
     * Create the message body.
     *
     * @param \Psr\Http\Message\StreamInterface|string $html
     *
     * @throws \Viserio\Component\Contract\Http\Exception\InvalidArgumentException if $html is neither a string or stream
     *
     * @return \Psr\Http\Message\StreamInterface
     */
    private function createBody($html): StreamInterface
    {
        if ($html instanceof StreamInterface) {
            return $html;
        }

        if (! \is_string($html)) {
            throw new InvalidArgumentException(\sprintf(
                'Invalid content [%s] provided to %s',
                (\is_object($html) ? \get_class($html) : \gettype($html)),
                __CLASS__
            ));
        }

        $body = new Stream(\fopen('php://temp', 'w+b'));
        $body->write($html);
        $body->rewind();

        return $body;
    }
}