<?php
/**
 * Class used by the ProxyObject to generate the actual proxy pbject.
 *
 * NOTICE:
 * This class should not be used directly. Always use ProxyObject or ProxyObjectBuilder to
 * create a proxy of your class.
 *
 * Copyright (c) 2010-2011, Bastian Feder <github@bastian-feder.de>.
 * All rights reserved.
 * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License Version 2.0, January 2004
 *             Licensed under the Apache License, Version 2.0 (the "License");
 *             you may not use this file except in compliance with the License.
 *             You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 * @copyright  2010-2011 Bastian Feder <github@bastian-feder.de>
 * @author     Bastian Feder <github@bastian-feder.de>
 * @author     Thomas Weinert <thomas@weinert.info>
 * @license    http://www.apache.org/licenses/LICENSE-2.0
 * @link       https://github.com/lapistano/proxy-object
 * @package    Unittests
 * @subpackage ProxyObject
 */

namespace lapistano\ProxyObject;

/**
 * Path to this module.
 * @var string
 */
$modulePath = __DIR__;

if (version_compare(\PHPUnit_Runner_Version::id(), '3.5', '<')) {
    include_once('PHPUnit/Util/Class.php');
    include_once('PHPUnit/Util/Filter.php');
    include_once('PHPUnit/Framework/Exception.php');
}

/**
 * Class used by the ProxyObject to generate the actual proxy pbject.
 *
 * @copyright  2010-2011 Bastian Feder <github@bastian-feder.de>
 * @author     Bastian Feder <github@bastian-feder.de>
 * @author     Thomas Weinert <thomas@weinert.info>
 * @license    http://www.apache.org/licenses/LICENSE-2.0
 * @link       https://github.com/lapistano/proxy-object
 * @package    Unittests
 * @subpackage ProxyObject
 *
 */
class Generator
{
    /**
     * Contain elements worth caching.
     * @var array
     */
    protected static $cache = array();

    /**
     * List of methods not to be reflected.
     * @var array
     */
    protected static $blacklistedMethodNames = array(
        '__clone' => true,
        '__destruct' => true,
        'abstract' => true,
        'and' => true,
        'array' => true,
        'as' => true,
        'break' => true,
        'case' => true,
        'catch' => true,
        'class' => true,
        'clone' => true,
        'const' => true,
        'continue' => true,
        'declare' => true,
        'default' => true,
        'do' => true,
        'else' => true,
        'elseif' => true,
        'enddeclare' => true,
        'endfor' => true,
        'endforeach' => true,
        'endif' => true,
        'endswitch' => true,
        'endwhile' => true,
        'extends' => true,
        'final' => true,
        'for' => true,
        'foreach' => true,
        'function' => true,
        'global' => true,
        'goto' => true,
        'if' => true,
        'implements' => true,
        'interface' => true,
        'instanceof' => true,
        'namespace' => true,
        'new' => true,
        'or' => true,
        'private' => true,
        'protected' => true,
        'public' => true,
        'static' => true,
        'switch' => true,
        'throw' => true,
        'try' => true,
        'use' => true,
        'var' => true,
        'while' => true,
        'xor' => true
    );

    /**
     * Switch to decide if dedicated properties are to be exposed or not.
     * @var boolean
     */
    protected static $exposeProperties = false;

    /**
     * Switch to decide if dedicated methods are to be exposed or not.
     * @var boolean
     */
    protected static $exposeMethods = false;

    /**
     * Gets the data to be used for the actual reflection.
     *
     * If the class has already been reflected in the same configuration,
     * it will be fetched from the local cache.
     *
     * @static
     *
     * @param string     $originalClassName Name of the class to be reflected.
     * @param array|null $methods           (Optional) List of methods to be exposed.
     * @param array|null $properties        (Optional) List of properties to be exposed.
     * @param string     $proxyClassName    (Optional) Name to be used for the reflected class.
     * @param bool       $callAutoload      (Optional) Switch to run the autoloader.
     *
     * @return array The data to be used for the actual reflection.
     */
    public static function generate($originalClassName, array $methods = null, array $properties = null,
                                    $proxyClassName = '', $callAutoload = false)
    {

        self::$exposeMethods = false;
        self::$exposeProperties = false;

        if ($proxyClassName == '') {
            $key = md5(
                $originalClassName . serialize($methods) . serialize($properties)
            );

            if (isset(self::$cache[$key])) {
                return self::$cache[$key];
            }
        }

        if (!empty($methods)) {
            self::$exposeMethods = true;
        }

        if (!empty($properties)) {
            self::$exposeProperties = true;
        }

        $proxy = self::generateProxy(
            $originalClassName, $methods, $properties, $proxyClassName, $callAutoload
        );

        if (isset($key)) {
            self::$cache[$key] = $proxy;
        }

        return $proxy;
    }

    /**
     * Gets the arguments the proxied method expectes.
     *
     * @param \ReflectionMethod $method
     *
     * @return array List of parameters to be passed to the proxied method.
     */
    public static function getMethodCallParameters($method)
    {
        $parameters = array();
        foreach ($method->getParameters() as $i => $parameter) {
            $parameters[] = '$' . $parameter->getName();
        }

        return join(', ', $parameters);
    }

    /**
     * Generates the data to be used for the actual reflection.
     *
     * @static
     *
     * @param string     $originalClassName Name of the class to be reflected.
     * @param array|null $methods           (Optional) List of methods to be exposed.
     * @param array|null $properties        (Optional) List of properties to be exposed.
     * @param string     $proxyClassName    (Optional) Name to be used for the reflected class.
     * @param bool       $callAutoload      (Optional) Switch to run the autoloader.
     *
     * @return array The data to be used for the actual reflection.
     * @throws \lapistano\ProxyObject\GeneratorException
     */
    protected static function generateProxy($originalClassName, array $methods = null, array $properties = null,
                                            $proxyClassName = '', $callAutoload = false)
    {
        $templateDir = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;
        $classTemplate = self::createTemplateObject(
            $templateDir . 'proxied_class.tpl'
        );

        $proxyClassName = self::generateProxyClassName(
            $originalClassName, $proxyClassName
        );

        if (interface_exists($proxyClassName['fullClassName'], $callAutoload)) {
            throw new GeneratorException(sprintf(
                '"%s" is an interface.', $proxyClassName['fullClassName']
            ), GeneratorException::IS_INTERFACE);
        }

        if (!class_exists($proxyClassName['fullClassName'], $callAutoload)) {
            throw new GeneratorException(sprintf(
                'Class "%s" does not exists.', $proxyClassName['fullClassName']
            ), GeneratorException::CLASS_NOT_FOUND);
        }

        $class = new \ReflectionClass($proxyClassName['fullClassName']);

        if ($class->isFinal()) {
            throw new GeneratorException(sprintf(
                'Class "%s" is declared "final". Cannot create proxy.', $proxyClassName['fullClassName']
            ), GeneratorException::CLASS_IS_FINAL);
        }

        if (!empty($proxyClassName['namespaceName'])) {
            $prologue = 'namespace ' . $proxyClassName['namespaceName'] . ";\n\n";
        }

        $classTemplate->setVar(
            array(
                'prologue' => isset($prologue) ? $prologue : '',
                'class_declaration' => $proxyClassName['proxyClassName'] . ' extends ' . $originalClassName,
                'methods' => self::getProxiedMethods($proxyClassName['fullClassName'], $class, $methods),
                'properties' => self::getProxiedProperties($proxyClassName['fullClassName'], $class, $properties),
            )
        );

        return array(
            'code' => $classTemplate->render(),
            'proxyClassName' => $proxyClassName['proxyClassName'],
            'namespaceName' => $proxyClassName['namespaceName']
        );
    }

    /**
     * Generate string representing the set of properties to be reflected.
     *
     * @param string           $fullClassName
     * @param \ReflectionClass $class
     * @param array            $properties
     *
     * @return string
     * @throws \lapistano\ProxyObject\GeneratorException
     */
    protected static function getProxiedProperties($fullClassName, \ReflectionClass $class, array $properties = null)
    {
        $proxiedProperties = '';
        $templateDir = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;
        $proxyProperties = $class->getProperties(\ReflectionMethod::IS_PROTECTED | \ReflectionMethod::IS_PRIVATE);

        if (empty($properties)) {
            foreach ($proxyProperties as $property) {
                $proxiedProperties .= self::generateProxiedPropertyDefinition($templateDir, $property, $class);
            }
        } else {

            foreach ($properties as $propertyName) {

                if ($class->hasProperty($propertyName)) {
                    $property = $class->getProperty($propertyName);
                    $proxiedProperties .= self::generateProxiedPropertyDefinition($templateDir, $property, $class);
                    continue;
                }

                throw new GeneratorException(
                    sprintf(
                        'Class "%s" has no protected or private property "%s".', $fullClassName, $propertyName
                    ),
                    GeneratorException::NO_PROTECTED_OR_PRIVATE_PROPERTY_DEFINED
                );
            }
        }

        return $proxiedProperties;
    }

    /**
     * Generates the definition of a method to be proxied.
     *
     * @param string              $templateDir Location of the templates to be used to create the proxy.
     * @param \ReflectionProperty $property    Name of the property to be reflected.
     * @param \ReflectionClass    $class       Name of the class to be reflected.
     *
     * @return array Information about the method to be proxied.
     */
    protected static function generateProxiedPropertyDefinition($templateDir, \ReflectionProperty $property,
                                                                \ReflectionClass $class)
    {
        $template = self::createTemplateObject(
            $templateDir . 'proxied_property.tpl'
        );

        $property->setAccessible(true);
        $properties = $class->getDefaultProperties();
        $value = $properties[$property->getName()];

        if ($value === (array)$value) {
            $value = self::arrayToString($value);
            $value = sprintf(' = array(%s)', $value);
        } else {
            if ($value === (string)$value) {
                $value = sprintf(' = \'%s\'', $value);
            } else {
                if (is_object($value)) {
                    $value = '';
                } else {
                    if ($value === (float)$value) {
                        $value = sprintf(' = %f', $value);
                    }
                }
            }
        }

        $template->setVar(
            array(
                'keyword' => $property->isStatic() ? 'static ' : '',
                'property_name' => $property->getName(),
                'property_value' => $value
            )
        );

        return $template->render();
    }

    /**
     * Generate string representing the set of methods to be reflected.
     *
     * @param array            $fullClassName
     * @param \ReflectionClass $class
     * @param array            $methods
     *
     * @return array Information about the method to be proxied.
     *
     * @throws GeneratorException
     */
    protected static function getProxiedMethods($fullClassName, \ReflectionClass $class, array $methods = null)
    {
        $proxyMethods = array();
        $proxiedMethods = '';
        $templateDir = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR;

        if (is_array($methods) && count($methods) > 0) {

            foreach ($methods as $methodName) {

                if ($class->hasMethod($methodName)) {

                    $method = $class->getMethod($methodName);

                    if (self::canProxyMethod($method)) {
                        $proxyMethods[] = $method;
                        continue;
                    }

                    throw new GeneratorException(
                        sprintf('Can not proxy method "%s" of class "%s".', $methodName, $fullClassName),
                        GeneratorException::CANNOT_PROXY_METHOD
                    );
                }

                throw new GeneratorException(
                    sprintf('Class "%s" has no protected method "%s".', $fullClassName, $methodName),
                    GeneratorException::NO_PROTECTED_METHOD_DEFINED
                );
            }
        } else {

            $proxyMethods = self::canProxyMethods($class->getMethods(\ReflectionMethod::IS_PROTECTED));

            if (empty($proxyMethods ) && self::$exposeProperties === false) {
                throw new GeneratorException(
                    sprintf('Class "%s" has no protected methods.', $fullClassName),
                    GeneratorException::NO_PROTECTED_METHOD_DEFINED
                );
            }
        }

        foreach ($proxyMethods as $method) {
            $proxiedMethods .= self::generateProxiedMethodDefinition($templateDir, $method);
        }

        return $proxiedMethods;
    }

    /**
     * Generates a unique name to be used to identify the created proxyclass.
     *
     * @param  string $originalClassName Name of the class to be reflected.
     * @param  string $proxyClassName    Name to be used for the reflected class.
     *
     * @return array Information of the class to be reflected.
     */
    protected static function generateProxyClassName($originalClassName, $proxyClassName)
    {
        $classNameParts = explode('\\', $originalClassName);
        $namespaceName = '';
        $fullClassName = $originalClassName;

        if (count($classNameParts) > 1) {
            $originalClassName = array_pop($classNameParts);
            $namespaceName = implode('\\', $classNameParts);
            $fullClassName = $namespaceName . '\\' . $originalClassName;

            // eval does identifies namespaces with leading backslash as constant.
            $namespaceName = (0 === stripos($namespaceName, '\\') ? substr($namespaceName, 1) : $namespaceName);
        }

        if ($proxyClassName == '') {
            do {
                $proxyClassName = 'Proxy_' . $originalClassName . '_' . substr(md5(microtime()), 0, 8);
            } while (class_exists($proxyClassName, false));
        }

        return array(
            'proxyClassName' => $proxyClassName,
            'className' => $originalClassName,
            'fullClassName' => $fullClassName,
            'namespaceName' => $namespaceName
        );
    }

    /**
     * Generates the definition of a method to be proxied.
     *
     * @param string            $templateDir Location of the templates to be used to create the proxy.
     * @param \ReflectionMethod $method      Name of the method to be reflected.
     *
     * @return array Information about the method to be proxied.
     */
    protected static function generateProxiedMethodDefinition($templateDir, \ReflectionMethod $method)
    {
        $reference = '';

        if ($method->returnsReference()) {
            $reference = '&';
        }

        $template = self::createTemplateObject(
            $templateDir . 'proxied_method.tpl'
        );

        $template->setVar(
            array(
                'arguments_declaration' => self::getArgumentDeclaration($method),
                'arguments' => self::getMethodCallParameters($method),
                'method_name' => $method->getName(),
                'reference' => $reference
            )
        );

        return $template->render();
    }

    /**
     * Adds prefix for global namespace to the type hint of each parameter of the method.
     *
     * This method is only necessary 'cause PHPUnit is currently not able to detect the usage of
     * namespaces. Thus it does not add the '\' to a type hint.
     *
     * @param \ReflectionMethod $method
     *
     * @return string
     */
    protected static function getArgumentDeclaration(\ReflectionMethod $method)
    {
        $declarations = array();
        $parameters = explode(', ', self::getMethodParameters($method));

        foreach ($parameters as $parameter) {

            if (0 < strpos(trim($parameter), ' $') && false === strpos($parameter, 'array')) {
                $declarations[] = '\\' . $parameter;
                continue;
            }

            $declarations[] = $parameter;
        }

        return implode(', ', $declarations);
    }

    /**
     * Determine if the given method may be proxied.
     *
     * Since it is not possible to reflect a
     *  - constructor
     *  - final method
     *  - static method
     * those will cause this method to return false.
     * Also methods registered in the blacklist will cause this
     * method to return false.
     *
     * @param \ReflectionMethod $method Name of the method to be reflected.
     *
     * @return boolean True, if the given method may be reflected, else false.
     */
    protected static function canProxyMethod(\ReflectionMethod $method)
    {
        if ($method->isConstructor() || $method->isFinal() || $method->isStatic() || isset(self::$blacklistedMethodNames[$method->getName()])
        ) {
            return false;
        } elseif ($method->isProtected()) {
            return true;
        }

        return false;
    }

    /**
     * Filters the list of ReflectionMethods down to those actually proxyable.
     *
     * Proxyable methods are those which are NOT:
     *  - constructor
     *  - final method
     *  - static method
     *  - members of the self::blacklist
     *
     * @param array $methods
     *
     * @return \ReflectionMethod[]
     */
    protected static function canProxyMethods(array $methods)
    {
        $proxyMethods = array();

        foreach ($methods as $method) {
            if (self::canProxyMethod($method)) {
                $proxyMethods[] = $method;
            }
        }

        return $proxyMethods;
    }

    /**
     * Generates the template to be used to create a proxy object.
     *
     * The return value depends on the used version of PHPUnit.
     * If a version is 3.5 of higher Text_Template, else PHPUnit_Util_Template
     * is used.
     *
     * @param string $file The location of the template file to be used.
     *
     * @return \Text_Template The template object to create the proxy class.
     */
    protected static function createTemplateObject($file)
    {
        @include_once('Text/Template.php');

        return new \Text_Template($file);
    }

    /**
     * Converts the given array to its string represenation recursivly.
     *
     * @param array $value
     *
     * @return string
     */
    protected static function arrayToString(array $value)
    {
        if (empty($value)) {
            return '';
        }

        return self::traverseStructure(new \RecursiveArrayIterator($value));
    }

    /**
     * Walks recursivly throught an array and concatinates the found info to a string.
     *
     * @param \RecursiveIterator $iterator
     *
     * @return string
     */
    protected static function traverseStructure($iterator)
    {
        $children = '';
        while ($iterator->valid()) {

            if ($iterator->hasChildren()) {
                $current = 'array (' . self::traverseStructure($iterator->getChildren()) . '), ';
            } else {
                $current = "'" . $iterator->current() . "', ";
            }

            $key = $iterator->key();

            if (is_numeric($key)) {
                $children .= $key . " => " . $current;
            } else {
                $children .= "'" . $key . "' => " . $current;
            }

            $iterator->next();
        }

        return $children;
    }

    /**
     * Returns the parameters of a function or method.
     *
     * @param  \ReflectionFunction|\ReflectionMethod $method
     * @param  boolean                             $forCall
     * @return string
     * @since  Method available since Release 3.2.0
     */
    public static function getMethodParameters($method, $forCall = FALSE)
    {
        $parameters = array();

        foreach ($method->getParameters() as $i => $parameter) {
            $name = '$' . $parameter->getName();

            /* Note: PHP extensions may use empty names for reference arguments
             * or "..." for methods taking a variable number of arguments.
             */
            if ($name === '$' || $name === '$...') {
                $name = '$arg' . $i;
            }

            $default   = '';
            $reference = '';
            $typeHint  = '';

            if (!$forCall) {
                if ($parameter->isArray()) {
                    $typeHint = 'array ';
                }

                else if (version_compare(PHP_VERSION, '5.4.0', '>=') && $parameter->isCallable()) {
                    $typeHint = 'callable ';
                }

                else {
                    try {
                        $class = $parameter->getClass();
                    }

                    catch (\ReflectionException $e) {
                        $class = FALSE;
                    }

                    if ($class) {
                        $typeHint = $class->getName() . ' ';
                    }
                }

                if ($parameter->isDefaultValueAvailable()) {
                    $value   = $parameter->getDefaultValue();
                    $default = ' = ' . var_export($value, TRUE);
                }

                else if ($parameter->isOptional()) {
                    $default = ' = null';
                }

                if ($parameter->isPassedByReference()) {
                    $reference = '&';
                }
            }

            $parameters[] = $typeHint . $reference . $name . $default;
        }

        return join(', ', $parameters);
    }
}


<?php

namespace Ekyna\Bundle\CoreBundle\Form\Type;

use Doctrine\Common\Persistence\ObjectManager;
use Ekyna\Bundle\CoreBundle\Form\DataTransformer\ObjectToIdentifierTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Class EntitySearchType
 * @package Ekyna\Bundle\CoreBundle\Form\Type
 * @author Étienne Dauvergne <contact@ekyna.com>
 */
class EntitySearchType extends AbstractType
{
    /**
     * @var \Doctrine\Common\Persistence\ObjectManager
     */
    protected $om;

    /**
     * Constructor.
     *
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $repository = $this->om->getRepository($options['entity']);
        $builder
            ->addModelTransformer(
                new ObjectToIdentifierTransformer($repository)
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function finishView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['attr']['data-search'] = $options['search_route'];
        $view->vars['attr']['data-find']   = $options['find_route'];
        $view->vars['attr']['data-clear']  = intval($options['allow_clear']);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefaults([
                'entity'       => null,
                'search_route' => null,
                'find_route'   => null,
                'allow_clear'  => false,
            ])
            ->setRequired(['entity', 'search_route', 'find_route'])
            ->setAllowedTypes('entity',        'string')
            ->setAllowedTypes('search_route',  'string')
            ->setAllowedTypes('find_route',    'string')
            ->setAllowedTypes('allow_clear',   'bool')
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return 'text';
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
    	return 'ekyna_entity_search';
    }
}


<?php

namespace tourze\Http;

use Exception;
use Psr\Http\Message\ResponseInterface;
use tourze\Base\Helper\Mime;
use tourze\Base\Exception\BaseException;
use tourze\Base\Helper\Arr;
use tourze\Base\Helper\Cookie;
use tourze\Base\Base;
use tourze\Http\Request\Exception\RequestException;

/**
 * 请求响应对象
 *
 * @property int status
 * @package tourze\Http
 */
class Response extends Stream implements ResponseInterface
{

    /**
     * @var self 保存当前的response
     */
    public static $current = null;

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        $this->message = new Message;
    }

    /**
     * @param string $name
     * @return mixed
     */
    public function getHeader($name)
    {
        return $this->message->getHeader($name);
    }

    /**
     * @var int 返回的HTTP状态码
     */
    protected $_status = 200;

    /**
     * @return int
     */
    protected function getStatus()
    {
        return $this->_status;
    }

    /**
     * @param int $status
     * @throws BaseException
     */
    protected function setStatus($status)
    {
        if (array_key_exists($status, Http::$text))
        {
            $this->_status = (int) $status;
        }
        else
        {
            throw new BaseException(__METHOD__ . ' unknown status value : :value', [':value' => $status]);
        }
    }

    /**
     * 输出网页内容
     *
     * @return string
     */
    public function __toString()
    {
        return $this->body;
    }

    /**
     * 输出header信息
     *
     *       // 获取指定头信息
     *       $accept = $response->headers('Content-Type');
     *
     *       // 设置头信息
     *       $response->headers('Content-Type', 'text/html');
     *
     *       // 获取所有头信息
     *       $headers = $response->headers();
     *
     *       // 一次设置多个头信息
     *       $response->headers(['Content-Type' => 'text/html', 'Cache-Control' => 'no-cache']);
     *
     * @param mixed  $name
     * @param string $value
     * @return mixed
     */
    public function headers($name = null, $value = null)
    {
        if (is_array($name))
        {
            foreach ($name as $k => $v)
            {
                $this->message->withHeader($k, $v);
            }
            return $this;
        }

        if (null === $name)
        {
            return $this->message->getHeaders();
        }
        elseif (null === $value)
        {
            return $this->message->getHeaderLine($name);
        }

        $this->message->withHeader($name, $value);

        return $this;
    }

    /**
     * 设置或者读取cookie
     *
     *     // 读取cookie
     *     $cookies = $response->cookie();
     *
     *     // 写cookie
     *     $response->cookie('session', [
     *          'value' => $value,
     *          'expiration' => 12352234
     *     ]);
     *
     * @param  mixed  $key   cookie名
     * @param  string $value cookie值
     * @return $this|mixed
     */
    public function cookie($key = null, $value = null)
    {
        if (null === $key)
        {
            return $this->_cookies;
        }
        elseif ( ! is_array($key) && ! $value)
        {
            return Arr::get($this->_cookies, $key);
        }

        if (is_array($key))
        {
            foreach ($key as $k => $v)
            {
                $this->cookie($k, $v);
            }
        }
        else
        {
            if ( ! is_array($value))
            {
                $value = [
                    'value'      => $value,
                    'expiration' => Cookie::$expiration,
                ];
            }
            elseif ( ! isset($value['expiration']))
            {
                $value['expiration'] = Cookie::$expiration;
            }

            $this->_cookies[(string) $key] = $value;
        }

        return $this;
    }

    /**
     * 删除指定cookie
     *
     * @param  string $name
     * @return $this
     */
    public function deleteCookie($name)
    {
        unset($this->_cookies[$name]);
        return $this;
    }

    /**
     * 清空所有cookie
     *
     * @return $this
     */
    public function deleteCookies()
    {
        $this->cookies = [];
        return $this;
    }

    /**
     * 发送当前保存的header信息
     *
     * @param  bool     $replace  替换已经存在的header
     * @param  callback $callback 是否自定义处理函数
     * @return $this
     */
    public function sendHeaders($replace = false, $callback = null)
    {
        $protocol = $this->protocol;
        $status = $this->status;
        $cookies = $this->cookie();
        $headers = $this->headers();

        Base::getLog()->debug(__METHOD__ . ' begin to send header', [
            'protocol' => $protocol,
            'status'   => $status,
            'header'   => $headers,
            'cookie'   => $cookies,
        ]);

        $renderHeaders = [$protocol . ' ' . $status . ' ' . Arr::get(Http::$text, $status)];

        foreach ($headers as $header => $value)
        {
            if (is_array($value))
            {
                $value = implode(', ', $value);
            }

            // 格式化header的key
            $header = explode('-', $header);
            foreach ($header as $k => $v)
            {
                $header[$k] = ucfirst($v);
            }
            $header = implode('-', $header);

            $renderHeaders[] = $header . ': ' . $value;
        }

        if (isset($headers['content-type']) || isset($headers['Content-Type']))
        {
            // 已经有content-type了，这里就不做处理了
        }
        else
        {
            // 默认content-type
            $renderHeaders[] = 'Content-Type: ' . Base::$contentType . '; charset=' . $this->charset;
        }

        if (Base::$expose && ! isset($headers['x-powered-by']))
        {
            $renderHeaders[] = 'X-Powered-By: ' . Base::version();
        }

        if ($cookies)
        {
            $renderHeaders['Set-Cookie'] = $cookies;
        }

        if (is_callable($callback))
        {
            // Use the callback method to set header
            return call_user_func($callback, $this, $renderHeaders, $replace);
        }
        else
        {
            $this->_sendHeadersToPhp($renderHeaders, $replace);

            return $this;
        }
    }

    /**
     * 发送header信息
     *
     * @param  array $headers
     * @param  bool  $replace
     * @return $this
     */
    protected function _sendHeadersToPhp(array $headers, $replace)
    {
        if (Base::getHttp()->headersSent())
        {
            return $this;
        }

        foreach ($headers as $key => $line)
        {
            Base::getLog()->debug(__METHOD__ . ' send headers to php', [
                'key'   => $key,
                'value' => $line,
            ]);
            if ($key == 'Set-Cookie' && is_array($line))
            {
                Base::getLog()->debug(__METHOD__ . ' set cookie headers', $line);

                foreach ($line as $name => $value)
                {
                    Cookie::set($name, $value['value'], $value['expiration']);
                }
                continue;
            }

            Base::getHttp()->header($line, $replace);
        }

        return $this;
    }

    /**
     * 通过HTTP发送文件，达到下载文件的效果
     *
     *      // 下载一个已经存在的文件
     *      $response->sendFile('media/packages/package.zip');
     *
     *      // 将返回内容当做是下载处理：
     *      $response->body = $content;
     *      $response->sendFile(true, $filename);
     *
     * @param  string $filename 文件名、文件路径，如果设置为`true`则返回当前的response内容
     * @param  string $download 下载文件名，默认为当前要处理的文件名
     * @param  array  $options  其他选项
     * @throws BaseException
     */
    public function sendFile($filename, $download = null, array $options = null)
    {
        // 强制指定了mime
        if ( ! empty($options['mime_type']))
        {
            $mime = $options['mime_type'];
        }

        // 返回当前的响应内容，作为本次下载
        if (true === $filename)
        {
            if (empty($download))
            {
                throw new BaseException('Download name must be provided for streaming files');
            }

            $options['delete'] = false;

            if ( ! isset($mime))
            {
                // 根据扩展名，获取指定的mime类型
                $mime = Mime::getMimeFromExtension(strtolower(pathinfo($download, PATHINFO_EXTENSION)));
            }

            // 将响应内容保存到临时文件
            $fileData = (string) $this->message->body;
            $size = strlen($fileData);
            $file = tmpfile();
            fwrite($file, $fileData);

            unset($fileData);
        }
        else
        {
            $filename = realpath($filename);

            if (empty($download))
            {
                $download = pathinfo($filename, PATHINFO_BASENAME);
            }

            $size = filesize($filename);

            if ( ! isset($mime))
            {
                $mime = Mime::getMimeFromExtension(pathinfo($download, PATHINFO_EXTENSION));
            }

            $file = fopen($filename, 'rb');
        }

        if ( ! is_resource($file))
        {
            throw new BaseException('Could not read file to send: :file', [
                ':file' => $download,
            ]);
        }

        // inline和attachment的区别，主要在于浏览器遇到inline类型的下载时，会尝试直接在浏览器打开，而attachment则不会
        $disposition = Arr::get($options, 'inline') ? 'inline' : 'attachment';

        $temp = $this->_calculateByteRange($size);
        $start = array_shift($temp);
        $end = array_shift($temp);

        if ( ! empty($options['resumable']))
        {
            if ($start > 0 || $end < ($size - 1))
            {
                // Partial Content
                $this->status = 206;
            }

            $this->headers('content-range', 'bytes ' . $start . '-' . $end . '/' . $size);
            $this->headers('accept-ranges', 'bytes');
        }

        $this->headers('content-disposition', $disposition . '; filename="' . $download . '"');
        $this->headers('content-type', $mime);
        $this->headers('content-length', (string) (($end - $start) + 1));

        $this->sendHeaders();

        // @notice 此方法在cli环境下可能无效

        while (ob_get_level())
        {
            ob_end_flush();
        }

        ignore_user_abort(true);

        $prevTimeLimit = ini_get('max_execution_time');
        @set_time_limit(0);

        // 16K
        $block = 1024 * 16;

        fseek($file, $start);

        while ( ! feof($file) && ($pos = ftell($file)) <= $end)
        {
            if (connection_aborted())
            {
                break;
            }

            if ($pos + $block > $end)
            {
                $block = $end - $pos + 1;
            }
            echo fread($file, $block);
            flush();
        }

        fclose($file);

        if ( ! empty($options['delete']))
        {
            try
            {
                unlink($filename);
            }
            catch (Exception $e)
            {
                Base::getLog()->error($e->getMessage(), [
                    'code' => $e->getCode(),
                ]);
            }
        }

        // 停止执行
        @set_time_limit($prevTimeLimit);
        Base::getHttp()->end();
    }

    /**
     * 渲染当前响应对象
     *
     * @return string
     */
    public function render()
    {
        Base::getLog()->debug(__METHOD__ . ' render response object');

        if ( ! $this->headers('content-type'))
        {
            $this->headers('content-type', Base::$contentType . '; charset=' . $this->charset);
        }

        $this->headers('content-length', (string) $this->contentLength);

        if (Base::$expose)
        {
            $this->headers('user-agent', Base::version());
        }

        if ($this->cookies)
        {
            if (extension_loaded('http'))
            {
                $this->headers('set-cookie', http_build_cookie($this->cookies));
            }
            else
            {
                $cookies = [];
                foreach ($this->cookies as $key => $value)
                {
                    $string = $key . '=' . $value['value'] . '; expires=' . date('l, d M Y H:i:s T', $value['expiration']);
                    $cookies[] = $string;
                }
                $this->headers('set-cookie', $cookies);
            }
        }

        $output = $this->protocol
            . ' '
            . $this->status
            . ' '
            . Arr::get(Http::$text, $this->status)
            . "\r\n";
        $output .= $this->message->headerLines;
        $output .= $this->message->body;

        return $output;
    }

    /**
     * 根据响应内容来生成E-Tag
     *
     * @throws RequestException
     * @return string
     */
    public function generateEtag()
    {
        if ('' === $this->body)
        {
            throw new RequestException('No response yet associated with request - cannot auto generate resource ETag');
        }

        return '"' . sha1($this->render()) . '"';
    }

    /**
     * 解析HTTP_RANGE
     *
     * @return array|false
     */
    protected function _parseByteRange()
    {
        if ($httpRange = Arr::get($_SERVER, 'HTTP_RANGE'))
        {
            Base::getLog()->debug(__METHOD__ . ' get HTTP_RANGE', [
                'string' => $httpRange,
            ]);
            preg_match_all('/(-?[0-9]++(?:-(?![0-9]++))?)(?:-?([0-9]++))?/', $httpRange, $matches, PREG_SET_ORDER);
            return $matches[0];
        }
        else
        {
            return false;
        }
    }

    /**
     * 计算要发送的字节范围
     *
     * @param  int $size
     * @return array
     */
    protected function _calculateByteRange($size)
    {
        $start = 0;
        $end = $size - 1;

        if ($range = $this->_parseByteRange())
        {
            // HTTP_RANGE中读取数据
            $start = Arr::get($range, 1, $start);

            if ('-' === Arr::get($range, 0))
            {
                // 负数表示返回从后面开始算起的字节数
                $start = $size - abs($start);
            }

            $end = Arr::get($range, 2, $end);
        }

        // 格式化数值，保证在范围内
        $start = abs(intval($start));
        $end = min(abs(intval($end)), $size - 1);
        $start = ($end < $start) ? 0 : max($start, 0);

        return [
            $start,
            $end,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getProtocolVersion()
    {
        return $this->message->protocolVersion;
    }

    /**
     * {@inheritdoc}
     */
    public function withProtocolVersion($version)
    {
        $this->message->withProtocolVersion($version);
        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function hasHeader($name)
    {
        return $this->message->hasHeader($name);
    }

    /**
     * {@inheritdoc}
     */
    public function getHeaderLine($name)
    {
        return $this->message->getHeaderLine($name);
    }

    /**
     * {@inheritdoc}
     */
    public function withHeader($name, $value)
    {
        $this->message->withHeader($name, $value);
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function withAddedHeader($name, $value)
    {
        $this->message->withAddedHeader($name, $value);
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function withoutHeader($name)
    {
        $this->message->withoutHeader($name);
        return $this;
    }

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

    /**
     * {@inheritdoc}
     */
    public function withStatus($code, $reasonPhrase = '')
    {
        $this->status = $code;
        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getReasonPhrase()
    {
        return Arr::get(Http::$text, $this->status, '');
    }
}


<?php

namespace Potherca\Katwizy\Composer;

use Composer\Script\Event;
use Potherca\Katwizy\Immutable\Command;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ProcessBuilder;

/**
 * Makes it trivial to add commands that are to be called from the Composer Scripts
 */
abstract class AbstractScriptEventHandler
{
    ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
    const ERROR_METHOD_NOT_CALLABLE = '<warning>Method "%s" could not be called</warning>';
    const ERROR_CLASS_NOT_LOADABLE = '<warning>Class "%s" could not be auto-loaded</warning>';

    /** @var  ProcessBuilder */
    private $processBuilder;

    //////////////////////////// SETTERS AND GETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\
    /**
     * Returns a list of commands to run
     *
     * To only call certain scripts on specific events, get the event name (with
     * `$event->getName()`) and check it against the available events in
     * Composer\Script\ScriptEvents. The most commonly used events are:
     *
     * - ScriptEvents::POST_INSTALL_CMD
     * - ScriptEvents::POST_UPDATE_CMD
     *
     * Command provided by the Symfony `console` command should be marked as
     * `COMMAND_TYPE_SYMFONY`. These will be handled the same as COMMAND_TYPE_SHELL
     *
     * @param Event $event
     *
     * @return Command[]
     *
     * @throws \InvalidArgumentException
     *
     */
    abstract public function getCommands(Event $event);

    /**
     * @return ProcessBuilder
     */
    protected function getProcessBuilder()
    {
        if ($this->processBuilder === null) {
            $this->processBuilder = new ProcessBuilder();
        }
        return $this->processBuilder;
    }

    //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    final public static function handleEvent(Event $event)
    {
        $self = new static();

        $commands = $self->getCommands($event);

        array_walk(
            $commands,
            function (Command $command) use ($self, $event) {
                $self->executeCommand($event, $command);
            }
        );
    }

    ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    /**
     * @param Event $event
     * @param Command $command
     *
     * @throws \InvalidArgumentException
     */
    protected function executeCommand(Event $event, Command $command)
    {
        switch ($command->getType()) {
            case $command::COMMAND_TYPE_PHP:
                $method = 'executePhpCommand';
                break;

            case $command::COMMAND_TYPE_SHELL:
            case $command::COMMAND_TYPE_SYMFONY:
                $method = 'executeShellCommand';
                break;

            default:
                throw new \InvalidArgumentException(
                    sprintf('Command type "%s" is not supported', $command->getType())
                );
        }

        try {
            $this->$method($event, $command);
        } catch (ProcessFailedException $e) {
            $event->getIO()->writeError($e->getMessage());
        }
    }

    protected function executeShellCommand(Event $event, Command $command)
    {
        $callable = $command->getCallable();
        $arguments = $command->getArguments();

        if ($command->getType() === $command::COMMAND_TYPE_SYMFONY) {
            $binDir = $event->getComposer()->getConfig()->get('bin-dir');
            array_unshift($arguments, $callable);
            $callable = sprintf('%s/console', $binDir);
        }

        $processBuilder = $this->getProcessBuilder();
        $process = $processBuilder->setPrefix($callable)
            ->setArguments($arguments)
            ->getProcess()
        ;

        $process->mustRun();

        $event->getIO()->write($process->getOutput());
    }

    protected function executePhpCommand(Event $event, Command $command)
    {
        $result = null;

        $callable = $command->getCallable();

        $className = substr($callable, 0, strpos($callable, '::'));
        $methodName = substr($callable, strpos($callable, '::') + 2);

        if (class_exists($className) === false) {
            $event->getIO()->writeError(sprintf(self::ERROR_CLASS_NOT_LOADABLE, $className));
        } elseif (is_callable($callable) === false) {
            $event->getIO()->writeError(sprintf(self::ERROR_METHOD_NOT_CALLABLE, $callable));
        } else {
            $event->getIO()->write(sprintf('<info>Calling PHP method "%s"</info>', $callable));
            $result = $className::$methodName($event);
        }

        return $result;
    }
}

/*EOF*/


<?php

namespace Reaction\I18n;

use Reaction;
use Reaction\Cache\ExpiringCacheInterface;
use Reaction\Exceptions\InvalidConfigException;
use Reaction\Db\DatabaseInterface;
use Reaction\Db\Expressions\Expression;
use Reaction\Db\Query;
use Reaction\DI\Instance;
use Reaction\Helpers\ArrayHelper;
use Reaction\Promise\ExtendedPromiseInterface;
use function Reaction\Promise\resolve;

/**
 * DbMessageSource extends [[MessageSource]] and represents a message source that stores translated
 * messages in database.
 *
 * The database must contain the following two tables: source_message and message.
 *
 * The `source_message` table stores the messages to be translated, and the `message` table stores
 * the translated messages. The name of these two tables can be customized by setting [[sourceMessageTable]]
 * and [[messageTable]], respectively.
 *
 * The database connection is specified by [[db]]. Database schema could be initialized by applying migration:
 *
 * ```
 * console migrate --migrationPath=@reaction/I18n/Migrations/
 * ```
 *
 * or, if you added 'Reaction\I18n\Migrations' namespace to console 'migrate' controller settings, then just
 *
 * ```
 * console migrate
 * ```
 *
 * If you don't want to use migration and need SQL instead, files for all databases are in migrations directory.
 */
class DbMessageSource extends MessageSource
{
    /**
     * @var DatabaseInterface|array|string the DB connection object or the application component ID of the DB connection.
     *
     * After the DbMessageSource object is created, if you want to change this property, you should only assign
     * it with a DB connection object.
     *
     * This can also be a configuration array for creating the object.
     */
    public $db = 'db';
    /**
     * @var ExpiringCacheInterface|array|string the cache object or the application component ID of the cache object.
     * The messages data will be cached using this cache object.
     * Note, that to enable caching you have to set [[enableCaching]] to `true`, otherwise setting this property has no effect.
     *
     * After the DbMessageSource object is created, if you want to change this property, you should only assign
     * it with a cache object.
     *
     * This can also be a configuration array for creating the object.
     * @see cachingDuration
     * @see enableCaching
     */
    public $cache = 'cache';
    /**
     * @var string the name of the source message table.
     */
    public $sourceMessageTable = '{{%source_message}}';
    /**
     * @var string the name of the translated message table.
     */
    public $messageTable = '{{%message}}';
    /**
     * @var int the time in seconds that the messages can remain valid in cache.
     * @see enableCaching
     */
    public $cachingDuration = 3600;
    /**
     * @var bool whether to enable caching translated messages
     */
    public $enableCaching = false;
    /**
     * @var array|null All used categories
     */
    protected $_categories;

    /**
     * Initializes the DbMessageSource component.
     * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
     * Configured [[cache]] component would also be initialized.
     * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid.
     */
    public function init()
    {
        parent::init();
        $this->db = Instance::ensure($this->db, DatabaseInterface::class);
        if ($this->enableCaching) {
            $this->cache = Instance::ensure($this->cache, 'Reaction\Cache\ExpiringCacheInterface');
        }
    }

    /**
     * Loads the message translation for the specified language and category.
     * If translation for specific locale code such as `en-US` isn't found it
     * tries more generic `en`.
     *
     * @param string $category the message category
     * @param string $language the target language
     * @return ExtendedPromiseInterface array the loaded messages. The keys are original messages, and the values
     * are translated messages.
     */
    protected function loadMessages($category, $language)
    {
        if ($this->enableCaching) {
            $cacheKey = [
                __CLASS__,
                $category,
                $language,
            ];
            return $this->cache
                ->get($cacheKey)
                ->then(function($data) {
                    return $data !== null ? $data : Reaction\Promise\reject(null);
                })->otherwise(function() use ($category, $language, $cacheKey) {
                    $messages = [];
                    return $this->loadMessagesFromDb($category, $language)
                        ->then(function($messagesDb) use (&$messages, $cacheKey) {
                            $messages = $messagesDb;
                            return $this->cache->set($cacheKey, $messagesDb, $this->cachingDuration);
                        })->then(function() use (&$messages) {
                            return $messages;
                        });
                });
        }

        return $this->loadMessagesFromDb($category, $language);
    }

    /**
     * Loads the messages from database.
     * You may override this method to customize the message storage in the database.
     * @param string $category the message category.
     * @param string $language the target language.
     * @return ExtendedPromiseInterface with array the messages loaded from database.
     */
    protected function loadMessagesFromDb($category, $language)
    {
        $mainQuery = (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation'])
            ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable])
            ->where([
                't1.id' => new Expression('[[t2.id]]'),
                't1.category' => $category,
                't2.language' => $language,
            ]);

        $fallbackLanguage = substr($language, 0, 2);
        $fallbackSourceLanguage = substr($this->sourceLanguage, 0, 2);

        if ($fallbackLanguage !== $language) {
            $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackLanguage), true);
        } elseif ($language === $fallbackSourceLanguage) {
            $mainQuery->union($this->createFallbackQuery($category, $language, $fallbackSourceLanguage), true);
        }

        return $mainQuery->createCommand($this->db)
            ->queryAll()
            ->then(function($messages) {
                return ArrayHelper::map($messages, 'message', 'translation');
            });
    }

    /**
     * The method builds the [[Query]] object for the fallback language messages search.
     * Normally is called from [[loadMessagesFromDb]].
     *
     * @param string $category the message category
     * @param string $language the originally requested language
     * @param string $fallbackLanguage the target fallback language
     * @return Query
     * @see loadMessagesFromDb
     */
    protected function createFallbackQuery($category, $language, $fallbackLanguage)
    {
        return (new Query())->select(['message' => 't1.message', 'translation' => 't2.translation'])
            ->from(['t1' => $this->sourceMessageTable, 't2' => $this->messageTable])
            ->where([
                't1.id' => new Expression('[[t2.id]]'),
                't1.category' => $category,
                't2.language' => $fallbackLanguage,
            ])->andWhere([
                'NOT IN', 't2.id', (new Query())->select('[[id]]')->from($this->messageTable)->where(['language' => $language]),
            ]);
    }

    /**
     * Find all categories used in message source
     * @return ExtendedPromiseInterface
     */
    protected function findAllCategories()
    {
        if (isset($this->_categories)) {
            return resolve($this->_categories);
        }
        return (new Query())->select(['category'])
            ->from($this->sourceMessageTable)
            ->orderBy(['category' => SORT_ASC])
            ->groupBy(['category'])
            ->column($this->db)
            ->then(function($categories) {
                return $this->_categories = $categories;
            });
    }
}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja;

use Valkyrja\Annotations\Annotations;
use Valkyrja\Client\Client;
use Valkyrja\Console\Console;
use Valkyrja\Console\Kernel as ConsoleKernel;
use Valkyrja\Container\Container;
use Valkyrja\Crypt\Crypt;
use Valkyrja\Debug\Debug;
use Valkyrja\Dispatcher\Dispatcher;
use Valkyrja\Env\Env;
use Valkyrja\Events\Events;
use Valkyrja\Exceptions\InvalidContainerImplementation;
use Valkyrja\Exceptions\InvalidDispatcherImplementation;
use Valkyrja\Exceptions\InvalidEventsImplementation;
use Valkyrja\Filesystem\Filesystem;
use Valkyrja\Http\Exceptions\HttpException;
use Valkyrja\Http\Exceptions\HttpRedirectException;
use Valkyrja\Http\JsonResponse;
use Valkyrja\Http\Kernel;
use Valkyrja\Http\RedirectResponse;
use Valkyrja\Http\Request;
use Valkyrja\Http\Response;
use Valkyrja\Http\ResponseBuilder;
use Valkyrja\Http\StatusCode;
use Valkyrja\Logger\Logger;
use Valkyrja\Mail\Mail;
use Valkyrja\ORM\EntityManager;
use Valkyrja\Path\PathGenerator;
use Valkyrja\Path\PathParser;
use Valkyrja\Routing\Router;
use Valkyrja\Session\Session;
use Valkyrja\Support\Directory;
use Valkyrja\View\View;

/**
 * Class Application.
 *
 * @author Melech Mizrachi
 */
class Valkyrja implements Application
{
    /**
     * Get the instance of the application.
     *
     * @var \Valkyrja\Application
     */
    protected static $app;

    /**
     * Whether the application was setup.
     *
     * @var bool
     */
    protected static $setup = false;

    /**
     * Application env.
     *
     * @var \Valkyrja\Env\Env|\env\Env
     */
    protected static $env;

    /**
     * Application config.
     *
     * @var array
     */
    protected static $config;

    /**
     * Get the instance of the container.
     *
     * @var \Valkyrja\Container\Container
     */
    protected static $container;

    /**
     * Get the instance of the dispatcher.
     *
     * @var \Valkyrja\Dispatcher\Dispatcher
     */
    protected static $dispatcher;

    /**
     * Get the instance of the events.
     *
     * @var \Valkyrja\Events\Events
     */
    protected static $events;

    /**
     * Is the app using a compiled version?
     *
     * @var bool
     */
    protected $isCompiled = false;

    /**
     * Application constructor.
     *
     * @param array $config [optional] The config to use
     *
     * @throws \Valkyrja\Exceptions\InvalidContainerImplementation
     * @throws \Valkyrja\Exceptions\InvalidDispatcherImplementation
     * @throws \Valkyrja\Exceptions\InvalidEventsImplementation
     */
    public function __construct(array $config = null)
    {
        $this->setup($config);
    }

    /**
     * Setup the application.
     *
     * @param array $config [optional] The config to use
     * @param bool  $force  [optional] Whether to force a setup
     *
     * @throws \Valkyrja\Exceptions\InvalidContainerImplementation
     * @throws \Valkyrja\Exceptions\InvalidDispatcherImplementation
     * @throws \Valkyrja\Exceptions\InvalidEventsImplementation
     *
     * @return void
     */
    public function setup(array $config = null, bool $force = false): void
    {
        // If the application was already setup, no need to do it again
        if (self::$setup && false === $force) {
            return;
        }

        // Avoid re-setting up the app later
        self::$setup = true;
        // Set the app static
        self::$app = $this;
        // Ensure the env has been set
        self::setEnv();

        // If the VALKYRJA_START constant hasn't already been set
        if (! \defined('VALKYRJA_START')) {
            // Set a global constant for when the framework started
            \define('VALKYRJA_START', microtime(true));
        }

        // Bootstrap debug capabilities
        $this->bootstrapConfig($config);
        // Bootstrap debug capabilities
        $this->bootstrapDebug();
        // Bootstrap core functionality
        $this->bootstrapCore();
        // Bootstrap the container
        $this->bootstrapContainer();
        // Bootstrap setup
        $this->bootstrapSetup();
        // Bootstrap the timezone
        $this->bootstrapTimezone();
    }

    /**
     * Bootstrap the config.
     *
     * @param array $config [optional] The config
     *
     * @return void
     */
    protected function bootstrapConfig(array $config = null): void
    {
        $cacheFilePath = Directory::cachePath('config.php');

        // If we should use the config cache file
        if (is_file($cacheFilePath)) {
            // Get the config from the cache file's contents
            self::$config = require $cacheFilePath;

            return;
        }

        $config         = $config ?? [];
        $configFilePath = Directory::configPath('config.php');
        $configFilePath = is_file($configFilePath)
            ? $configFilePath
            : __DIR__ . '/Config/config.php';
        $defaultConfigs = require $configFilePath;

        self::$config = array_replace_recursive($defaultConfigs, $config);
        /** @var \Valkyrja\Support\Providers\Provider[] $providers */
        $providers = self::$config['providers'];

        foreach ($providers as $provider) {
            // Config providers are NOT deferred and will not follow the
            // deferred value
            $provider::publish($this);
        }
    }

    /**
     * Bootstrap debug capabilities.
     *
     * @return void
     */
    protected function bootstrapDebug(): void
    {
        // If debug is on, enable debug handling
        if (self::$config['app']['debug']) {
            // Debug to output exceptions
            Debug::enable(E_ALL, true);
        }
    }

    /**
     * Bootstrap core functionality.
     *
     * @throws \Valkyrja\Exceptions\InvalidContainerImplementation
     * @throws \Valkyrja\Exceptions\InvalidDispatcherImplementation
     * @throws \Valkyrja\Exceptions\InvalidEventsImplementation
     *
     * @return void
     */
    protected function bootstrapCore(): void
    {
        // The events class to use from the config
        $eventsImpl = self::$config['app']['events'];
        // The container class to use from the config
        $containerImpl = self::$config['app']['container'];
        // The dispatcher class to use from the config
        $dispatcherImpl = self::$config['app']['dispatcher'];

        // Set the events to a new instance of the events implementation
        self::$events = new $eventsImpl($this);

        // If the events implementation specified does not adhere to the events
        // contract
        if (! self::$events instanceof Events) {
            throw new InvalidEventsImplementation(
                'Invalid Events implementation'
            );
        }

        // Set the container to a new instance of the container implementation
        self::$container = new $containerImpl($this);

        // If the container implementation specified does not adhere to the
        // container contract
        if (! self::$container instanceof Container) {
            throw new InvalidContainerImplementation(
                'Invalid Container implementation'
            );
        }

        // Set the dispatcher to a new instance of the dispatcher implementation
        self::$dispatcher = new $dispatcherImpl($this);

        // If the dispatcher implementation specified does not adhere to the
        // dispatcher contract
        if (! self::$dispatcher instanceof Dispatcher) {
            throw new InvalidDispatcherImplementation(
                'Invalid Dispatcher implementation'
            );
        }
    }

    /**
     * Bootstrap the container.
     *
     * @return void
     */
    protected function bootstrapContainer(): void
    {
        // Set the application instance in the container
        self::$container->singleton(Application::class, $this);
        // Set the events instance in the container
        self::$container->singleton('env', self::$env);
        // Set the events instance in the container
        self::$container->singleton('config', self::$config);
        // Set the container instance in the container
        self::$container->singleton(Container::class, self::$container);
        // Set the dispatcher instance in the dispatcher
        self::$container->singleton(Dispatcher::class, self::$dispatcher);
        // Set the events instance in the container
        self::$container->singleton(Events::class, self::$events);
    }

    /**
     * Bootstrap main components setup.
     *
     * @return void
     */
    protected function bootstrapSetup(): void
    {
        // Setup the container
        // NOTE: Not done in container construct to avoid container()
        // helper returning null self::$container
        self::$container->setup();
        // Setup the events
        // NOTE: Not done in events construct to avoid container dependency
        // not existing within setup (for ListenerAnnotations)
        self::$events->setup();
    }

    /**
     * Bootstrap the timezone.
     *
     * @return void
     */
    protected function bootstrapTimezone(): void
    {
        date_default_timezone_set(self::$config['app']['timezone']);
    }

    /**
     * Get the application instance.
     *
     * @return \Valkyrja\Application
     */
    public static function app(): Application
    {
        return self::$app;
    }

    /**
     * Get an environment variable.
     *
     * @param string $variable [optional] The variable to get
     * @param string $default  [optional] The default value to return
     *
     * @return mixed|\Valkyrja\Env\Env||config|Env
     */
    public static function env(string $variable = null, $default = null)
    {
        // If there was no variable requested
        if (null === $variable) {
            // Return the env class
            return static::getEnv();
        }

        // If the env has this variable defined and the variable isn't null
        if (
            \defined(static::getEnv() . '::' . $variable)
            && null !== $env = \constant(static::getEnv() . '::' . $variable)
        ) {
            // Return the variable
            return $env;
        }

        // Otherwise return the default
        return $default;
    }

    /**
     * Get the environment variables class.
     *
     * @return \Valkyrja\Env\Env||config|Env
     */
    public static function getEnv(): string
    {
        return self::$env ?? self::$env = Env::class;
    }

    /**
     * Set the environment variables class.
     *
     * @param string $env [optional] The env file to use
     *
     * @return void
     */
    public static function setEnv(string $env = null): void
    {
        // Set the env class to use
        self::$env = $env ?? self::$env ?? Env::class;
    }

    /**
     * Get the config.
     *
     * @param string $key     [optional] The key to get
     * @param string $default [optional] The default value if the key is not
     *                        found
     *
     * @return mixed
     */
    public function config(string $key = null, $default = null)
    {
        // If no key was specified
        if (null === $key) {
            // Return all the entire config
            return self::$config;
        }

        // Explode the keys on period
        $keys = explode('.', $key);
        // Set the config to return
        $config = self::$config;

        // Iterate through the keys
        foreach ($keys as $configItem) {
            // Trying to get the item from the config
            // or load the default
            $config = $config[$configItem] ?? $default;

            // If the item was not found, might as well return out from here
            // instead of continuing to iterate through the remaining keys
            if ($default === $config) {
                return $default;
            }
        }

        // do while($current !== $default);

        // Return the found config
        return $config;
    }

    /**
     * Add to the global config array.
     *
     * @param array $newConfig The new config to add
     *
     * @return void
     */
    public function addConfig(array $newConfig): void
    {
        // Set the config within the application
        self::$config = array_replace_recursive(self::$config, $newConfig);
    }

    /**
     * Get the container instance.
     *
     * @return \Valkyrja\Container\Container
     */
    public function container(): Container
    {
        return self::$container;
    }

    /**
     * Get the dispatcher instance.
     *
     * @return \Valkyrja\Dispatcher\Dispatcher
     */
    public function dispatcher(): Dispatcher
    {
        return self::$dispatcher;
    }

    /**
     * Get the events instance.
     *
     * @return \Valkyrja\Events\Events
     */
    public function events(): Events
    {
        return self::$events;
    }

    /**
     * Get the application version.
     *
     * @return string
     */
    public function version(): string
    {
        return static::VERSION;
    }

    /**
     * Get the environment with which the application is running in.
     *
     * @return string
     */
    public function environment(): string
    {
        return self::$config['app']['env'];
    }

    /**
     * Whether the application is running in debug mode or not.
     *
     * @return string
     */
    public function debug(): string
    {
        return self::$config['app']['debug'];
    }

    /**
     * Get whether the application is using a compiled version.
     *
     * @return bool
     */
    public function isCompiled(): bool
    {
        return $this->isCompiled;
    }

    /**
     * Set the application as using compiled.
     *
     * @return void
     */
    public function setCompiled(): void
    {
        $this->isCompiled = true;
    }

    /**
     * Abort the application due to error.
     *
     * @param int      $statusCode The status code to use
     * @param string   $message    [optional] The Exception message to throw
     * @param array    $headers    [optional] The headers to send
     * @param int      $code       [optional] The Exception code
     * @param Response $response   [optional] The Response to send
     *
     * @throws \Valkyrja\Http\Exceptions\HttpException
     *
     * @return void
     */
    public function abort(
        int $statusCode = StatusCode::NOT_FOUND,
        string $message = '',
        array $headers = [],
        int $code = 0,
        Response $response = null
    ): void {
        throw new HttpException($statusCode, $message, null, $headers, $code, $response);
    }

    /**
     * Redirect to a given uri, and abort the application.
     *
     * @param string $uri        [optional] The URI to redirect to
     * @param int    $statusCode [optional] The response status code
     * @param array  $headers    [optional] An array of response headers
     *
     * @throws \Valkyrja\Http\Exceptions\HttpRedirectException
     *
     * @return void
     */
    public function redirectTo(string $uri = null, int $statusCode = StatusCode::FOUND, array $headers = []): void
    {
        throw new HttpRedirectException($statusCode, $uri, null, $headers, 0);
    }

    /**
     * Return the annotations instance from the container.
     *
     * @return \Valkyrja\Annotations\Annotations
     */
    public function annotations(): Annotations
    {
        return $this->container()->getSingleton(Annotations::class);
    }

    /**
     * Return the client instance from the container.
     *
     * @return \Valkyrja\Client\Client
     */
    public function client(): Client
    {
        return $this->container()->getSingleton(Client::class);
    }

    /**
     * Return the console instance from the container.
     *
     * @return \Valkyrja\Console\Console
     */
    public function console(): Console
    {
        return $this->container()->getSingleton(Console::class);
    }

    /**
     * Return the console kernel instance from the container.
     *
     * @return \Valkyrja\Console\Kernel
     */
    public function consoleKernel(): ConsoleKernel
    {
        return $this->container()->getSingleton(ConsoleKernel::class);
    }

    /**
     * Return the crypt instance from the container.
     *
     * @return \Valkyrja\Crypt\Crypt
     */
    public function crypt(): Crypt
    {
        return $this->container()->getSingleton(Crypt::class);
    }

    /**
     * Return the entity manager instance from the container.
     *
     * @return \Valkyrja\ORM\EntityManager
     */
    public function entityManager(): EntityManager
    {
        return $this->container()->getSingleton(EntityManager::class);
    }

    /**
     * Return the filesystem instance from the container.
     *
     * @return \Valkyrja\Filesystem\Filesystem
     */
    public function filesystem(): Filesystem
    {
        return $this->container()->getSingleton(Filesystem::class);
    }

    /**
     * Return the kernel instance from the container.
     *
     * @return \Valkyrja\Http\Kernel
     */
    public function kernel(): Kernel
    {
        return $this->container()->getSingleton(Kernel::class);
    }

    /**
     * Return the logger instance from the container.
     *
     * @return \Valkyrja\Logger\Logger
     */
    public function logger(): Logger
    {
        return $this->container()->getSingleton(Logger::class);
    }

    /**
     * Return the mail instance from the container.
     *
     * @return \Valkyrja\Mail\Mail
     */
    public function mail(): Mail
    {
        return $this->container()->getSingleton(Mail::class);
    }

    /**
     * Return the path generator instance from the container.
     *
     * @return \Valkyrja\Path\PathGenerator
     */
    public function pathGenerator(): PathGenerator
    {
        return $this->container()->getSingleton(PathGenerator::class);
    }

    /**
     * Return the path parser instance from the container.
     *
     * @return \Valkyrja\Path\PathParser
     */
    public function pathParser(): PathParser
    {
        return $this->container()->getSingleton(PathParser::class);
    }

    /**
     * Return the request instance from the container.
     *
     * @return \Valkyrja\Http\Request
     */
    public function request(): Request
    {
        return $this->container()->getSingleton(Request::class);
    }

    /**
     * Return the router instance from the container.
     *
     * @return \Valkyrja\Routing\Router
     */
    public function router(): Router
    {
        return $this->container()->getSingleton(Router::class);
    }

    /**
     * Return a new response from the application.
     *
     * @param string $content    [optional] The content to set
     * @param int    $statusCode [optional] The status code to set
     * @param array  $headers    [optional] The headers to set
     *
     * @throws \InvalidArgumentException
     *
     * @return \Valkyrja\Http\Response
     */
    public function response(string $content = '', int $statusCode = StatusCode::OK, array $headers = []): Response
    {
        /** @var Response $response */
        $response = $this->container()->getSingleton(Response::class);

        if (\func_num_args() === 0) {
            return $response;
        }

        return $response->create($content, $statusCode, $headers);
    }

    /**
     * Return a new json response from the application.
     *
     * @param array $data       [optional] An array of data
     * @param int   $statusCode [optional] The status code to set
     * @param array $headers    [optional] The headers to set
     *
     * @throws \InvalidArgumentException
     *
     * @return \Valkyrja\Http\JsonResponse
     */
    public function json(array $data = [], int $statusCode = StatusCode::OK, array $headers = []): JsonResponse
    {
        /** @var JsonResponse $response */
        $response = $this->container()->getSingleton(JsonResponse::class);

        if (\func_num_args() === 0) {
            return $response;
        }

        return $response->createJson('', $statusCode, $headers, $data);
    }

    /**
     * Return a new json response from the application.
     *
     * @param string $uri        [optional] The URI to redirect to
     * @param int    $statusCode [optional] The response status code
     * @param array  $headers    [optional] An array of response headers
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Http\Exceptions\InvalidStatusCodeException
     *
     * @return \Valkyrja\Http\RedirectResponse
     */
    public function redirect(
        string $uri = null,
        int $statusCode = StatusCode::FOUND,
        array $headers = []
    ): RedirectResponse {
        /** @var RedirectResponse $response */
        $response = $this->container()->getSingleton(RedirectResponse::class);

        if (\func_num_args() === 0) {
            return $response;
        }

        return $response->createRedirect($uri, $statusCode, $headers);
    }

    /**
     * Return a new redirect response from the application for a given route.
     *
     * @param string $route      The route to match
     * @param array  $parameters [optional] Any parameters to set for dynamic
     *                           routes
     * @param int    $statusCode [optional] The response status code
     * @param array  $headers    [optional] An array of response headers
     *
     * @throws \InvalidArgumentException
     * @throws \Valkyrja\Http\Exceptions\InvalidStatusCodeException
     *
     * @return \Valkyrja\Http\RedirectResponse
     */
    public function redirectRoute(
        string $route,
        array $parameters = [],
        int $statusCode = StatusCode::FOUND,
        array $headers = []
    ): RedirectResponse {
        // Get the uri from the router using the route and parameters
        $uri = $this->router()->routeUrl($route, $parameters);

        return $this->redirect($uri, $statusCode, $headers);
    }

    /**
     * Return a new response from the application.
     *
     * @return \Valkyrja\Http\ResponseBuilder
     */
    public function responseBuilder(): ResponseBuilder
    {
        return $this->container()->getSingleton(ResponseBuilder::class);
    }

    /**
     * Return the session.
     *
     * @return \Valkyrja\Session\Session
     */
    public function session(): Session
    {
        return $this->container()->getSingleton(Session::class);
    }

    /**
     * Helper function to get a new view.
     *
     * @param string $template  [optional] The template to use
     * @param array  $variables [optional] The variables to use
     *
     * @return \Valkyrja\View\View
     */
    public function view(string $template = '', array $variables = []): View
    {
        /** @var \Valkyrja\View\View $view */
        $view = $this->container()->getSingleton(View::class);

        if (\func_num_args() === 0) {
            return $view;
        }

        return $view->make($template, $variables);
    }
}


<?php

namespace Framework;

use DirectoryIterator;

class Config {

    /**
     * List of files we have loaded into our configuration
     *
     * @var array
     */
    protected $files = array();

    /**
     * All the configuration items those configs contain.
     *
     * @var array
     */
    protected $items = array();

    /**
     * Load all of the files and the config items they contain.
     */
    public function __construct() {

        // If we haven't loaded any files yet find some
        if(empty($files)) {
            $this->files = $this->findFiles();

            foreach($this->files as $file) {
                $this->items[str_replace('.php', '', $file)] = $this->loadItems($file);
            }
        }
    }

    /**
     * Check if key exists
     *
     * @param $key
     *
     * @return bool
     */
    public function exists($key) {
        $item = array_get($this->items, $key);

        return isset($item);
    }

    /**
     * Get a configuration item.
     *
     * @param $key
     *
     * @return mixed
     */
    public function get($key) {
        if(!$this->exists($key)) {
            return false;
        }

        return array_get($this->items, $key);
    }

    public function put($key, $value) {
        array_set($this->items, $key, $value);

        return $value;
    }

    private function findFiles() {
        $files = array();
        foreach (new DirectoryIterator(app_path('config')) as $fileInfo) {
            if($fileInfo->isDot()) continue;

            array_push($files, $fileInfo->getFilename());
        }

        return $files;
    }

    /**
     * Loads the array into a variable.
     *
     * @param $file
     *
     * @return mixed
     */
    private function loadItems($file) {
        return require(app_path("config/$file"));
    }

}


<?php

declare(strict_types=1);

namespace Psi\Component\Description\Schema;

use Psi\Component\Description\DescriptorInterface;

/**
 * Description schema defines which descriptors are allowed to be
 * set on the description.
 */
class Schema
{
    private $definitions;

    public function __construct(array $extensions)
    {
        foreach ($extensions as $extension) {
            $this->register($extension);
        }
    }

    /**
     * Ensure that a given key is valid.
     *
     * This is called when the user attempts to access a descriptor.
     */
    public function validateKey(string $key)
    {
        if (false === isset($this->definitions[$key])) {
            throw new \InvalidArgumentException(sprintf(
                'Descriptor "%s" not a permitted descriptor key. Did you make a typo? Permitted descriptors: "%s"',
                $key,
                implode('", "', array_keys($this->definitions))
            ));
        }
    }

    /**
     * Validate the descriptor key AND ensures that the given
     * descriptor is of the correct type according to the schema.
     *
     * This is called when a descriptor is set on the description.
     */
    public function validate(string $key, DescriptorInterface $descriptor)
    {
        $this->validateKey($key);
        $definition = $this->definitions[$key];

        if (get_class($descriptor) !== $definition->getClass()) {
            throw new \InvalidArgumentException(sprintf(
                'Descriptor with key "%s" must be of class "%s", got "%s"',
                $key,
                $definition->getClass(),
                get_class($descriptor)
            ));
        }
    }

    /**
     * Return the schema definitions.
     *
     * @return Definition[]
     */
    public function getDefinitions(): array
    {
        return $this->definitions;
    }

    public function getDefinition($key): Definition
    {
        if (!isset($this->definitions[$key])) {
            throw new \InvalidArgumentException(sprintf(
                'Unknown definition "%s"', $key
            ));
        }

        return $this->definitions[$key];
    }

    private function register(ExtensionInterface $extension)
    {
        $builder = new Builder(get_class($extension));
        $extension->buildSchema($builder);

        foreach ($builder->getDefinitions() as $definition) {
            $key = sprintf('%s.%s', $extension->getName(), $definition->getKey());

            if (isset($this->definitions[$key])) {
                throw new \InvalidArgumentException(sprintf(
                    'Descriptor key "%s" for was already registered by "%s" extension',
                    $key, $this->definitions[$key]->getExtensionClass()
                ));
            }

            $this->definitions[$key] = $definition;
        }
    }
}


<?php

namespace webignition\WebResource;

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use webignition\InternetMediaType\Parser\ParseException as InternetMediaTypeParseException;
use webignition\InternetMediaType\Parser\Parser as InternetMediaTypeParser;
use webignition\InternetMediaTypeInterface\InternetMediaTypeInterface;
use webignition\WebResource\Exception\HttpException;
use webignition\WebResource\Exception\InvalidResponseContentTypeException;
use webignition\WebResource\Exception\TransportException;
use webignition\WebResource\JsonDocument\JsonDocument;
use webignition\WebResource\WebPage\WebPage;
use webignition\WebResourceInterfaces\RetrieverInterface;
use webignition\WebResourceInterfaces\WebResourceInterface;

class Retriever implements RetrieverInterface
{
    const DEFAULT_WEB_RESOURCE_MODEL = WebResource::class;
    const DEFAULT_ALLOWED_CONTENT_TYPES = [];
    const DEFAULT_ALLOW_UNKNOWN_RESOURCE_TYPES = true;

    /**
     * @var HttpClient
     */
    private $httpClient;

    /**
     * @var string[]
     */
    private $allowedContentTypes = self::DEFAULT_ALLOWED_CONTENT_TYPES;

    /**
     * @var bool
     */
    private $allowUnknownResourceTypes = self::DEFAULT_ALLOW_UNKNOWN_RESOURCE_TYPES;

    public function __construct(
        HttpClient $httpClient = null,
        array $allowedContentTypes = self::DEFAULT_ALLOWED_CONTENT_TYPES,
        bool $allowUnknownResourceTypes = self::DEFAULT_ALLOW_UNKNOWN_RESOURCE_TYPES
    ) {
        if (empty($httpClient)) {
            $httpClient = new HttpClient();
        }

        $this->setHttpClient($httpClient);
        $this->setAllowedContentTypes($allowedContentTypes);
        $this->setAllowUnknownResourceTypes($allowUnknownResourceTypes);
    }

    public function setHttpClient(HttpClient $httpClient)
    {
        $this->httpClient = $httpClient;
    }

    /**
     * {@inheritdoc}
     */
    public function setAllowedContentTypes($allowedContentTypes = [])
    {
        $this->allowedContentTypes = $allowedContentTypes;
    }

    /**
     * {@inheritdoc}
     */
    public function setAllowUnknownResourceTypes($allowUnknownResourceTypes = true)
    {
        $this->allowUnknownResourceTypes = $allowUnknownResourceTypes;
    }

    /**
     * @param RequestInterface $request
     *
     * @return WebResourceInterface
     *
     * A RetrieverExceptionInterface instance MUST be thrown when a resource
     * is retrieved with a status code other than 200.
     *
     * An InvalidContentTypeExceptionInterface instance MUST  be thrown when the content type of a resource
     * does not match one of those provided by setAllowedContentTypes() and when unknown resource types are not allowed.
     *
     * @throws HttpException
     * @throws InternetMediaTypeParseException
     * @throws TransportException
     * @throws InvalidResponseContentTypeException
     */
    public function retrieve(RequestInterface $request): WebResourceInterface
    {
        if (!$this->allowUnknownResourceTypes) {
            $headRequest = $request->withMethod('HEAD');

            $this->preVerifyContentType($headRequest);
        }

        $requestUri = $request->getUri();
        $response = null;

        try {
            $response = $this->httpClient->send($request, [
                'on_stats' => function (TransferStats $stats) use (&$requestUri) {
                    if ($stats->hasResponse()) {
                        $requestUri = $stats->getEffectiveUri();
                    }
                },
            ]);
        } catch (BadResponseException $badResponseException) {
            $response = $badResponseException->getResponse();
        } catch (RequestException $requestException) {
            throw new TransportException($request, $requestException);
        } catch (GuzzleException $guzzleException) {
            throw new TransportException(
                $request,
                new RequestException($guzzleException->getMessage(), $request, null, $guzzleException)
            );
        }

        $responseStatusCode = $response->getStatusCode();
        $isSuccessResponse = $responseStatusCode >= 200 && $responseStatusCode < 300;

        if (!$isSuccessResponse) {
            throw new HttpException($request, $response);
        }

        $modelClassName = $this->getModelClassNameFromContentTypeWithContentTypeVerification($request, $response);

        return new $modelClassName(WebResourceProperties::create([
            WebResourceProperties::ARG_URI => $requestUri,
            WebResourceProperties::ARG_RESPONSE => $response,
        ]));
    }

    /**
     * @param RequestInterface $request
     * @param ResponseInterface $response
     *
     * @return string
     *
     * @throws InternetMediaTypeParseException
     * @throws InvalidResponseContentTypeException
     */
    private function getModelClassNameFromContentTypeWithContentTypeVerification(
        RequestInterface $request,
        ResponseInterface $response
    ): string {
        $contentType = $this->getContentTypeFromResponse($response);
        $modelClassName = $this->getModelClassNameForContentType($contentType);

        $isAllowedContentType = in_array($contentType->getTypeSubtypeString(), $this->allowedContentTypes);

        if (!$isAllowedContentType && !$this->allowUnknownResourceTypes) {
            throw new InvalidResponseContentTypeException($contentType, $request, $response);
        }

        return $modelClassName;
    }

    private function getModelClassNameForContentType(InternetMediaTypeInterface $contentType): string
    {
        if (WebPage::models($contentType)) {
            return WebPage::class;
        }

        if (JsonDocument::models($contentType)) {
            return JsonDocument::class;
        }

        return WebResource::class;
    }

    /**
     * @param RequestInterface $request
     *
     * @return bool|null
     *
     * @throws InternetMediaTypeParseException
     * @throws InvalidResponseContentTypeException
     */
    private function preVerifyContentType(RequestInterface $request): ?bool
    {
        try {
            $response = $this->httpClient->send($request);
        } catch (RequestException $requestException) {
            return null;
        } catch (GuzzleException $guzzleException) {
            return null;
        }

        $this->getModelClassNameFromContentTypeWithContentTypeVerification($request, $response);

        return true;
    }

    /**
     * @param ResponseInterface $response
     *
     * @return InternetMediaTypeInterface
     *
     * @throws InternetMediaTypeParseException
     */
    private function getContentTypeFromResponse(ResponseInterface $response): InternetMediaTypeInterface
    {
        $mediaTypeParser = new InternetMediaTypeParser();
        $mediaTypeParser->setAttemptToRecoverFromInvalidInternalCharacter(true);
        $mediaTypeParser->setIgnoreInvalidAttributes(true);

        $contentTypeHeader = $response->getHeader('content-type');
        $contentTypeString = empty($contentTypeHeader)
            ? ''
            : $contentTypeHeader[0];

        return $mediaTypeParser->parse($contentTypeString);
    }
}


<?php

namespace Philip\Plugin;

use Philip\AbstractPlugin as BasePlugin;
use Philip\IRC\Response;
use Philip\IRC\Event;

/**
 * Adds an "answering machine" to the Philip IRC bot.
 *
 * @author Bill Israel <bill.israel@gmail.com>
 */
class AnsweringMachinePlugin extends BasePlugin
{
    /**
     * This plugin is made of two parts: the command to save messages, and the listener to deliver messages.
     *
     * !msg <recipient> <message> - saves a message for the recipient
     * Example Usage:
     *      !msg irc-buddy Call me whenever you get this
     *
     * NOTE: if the command is sent via private message, the message will be delivered via private message as well.
     */
    public function init()
    {
        $bot = $this->bot;

        /** The messages to deliver publicly to users */
        $public = array();

        /** The messages to deliver privately to users */
        $private = array();

        // Saves messages
        $this->bot->onMessages('/^!msg\s(\S+)\s(.*)/', function(Event $event) use ($bot, &$private, &$public) {
            $matches = $event->getMatches();
            $recipient = $matches[0];
            $sender = $event->getRequest()->getSendingUser();
            $msg = $matches[1];

            if ($event->getRequest()->isPrivateMessage()) {
                $queue =& $private;
            } else {
                $queue =& $public;
            }

            if (!isset($queue[$recipient])) {
                $queue[$recipient] = array();
            }

            $queue[$recipient][$msg] = $sender;

            $event->addResponse(Response::msg($event->getRequest()->getSource(), "Message for $recipient saved successfully."));
        });

        // Delivers messages
        $this->bot->onJoin(function(Event $event) use ($bot, &$public, &$private) {
            $joiner = $event->getRequest()->getSendingUser();
            $channel = $event->getRequest()->getSource();

            // Retrieve public messages
            if (in_array($joiner, array_keys($public))) {
                foreach($public[$joiner] as $message => $sender) {

                    if ($joiner == $sender) {
                        $msg = "$joiner: you left yourself a message - $message";
                    } else {
                        $msg = "$joiner: $sender left you a message - $message";
                    }

                    $event->addResponse(Response::msg($channel, $msg));
                    unset($public[$joiner][$message]);
                }
            }

            // Retrieve private messages
            if (in_array($joiner, array_keys($private))) {
                foreach($private[$joiner] as $message => $sender) {
                    if ($joiner == $sender) {
                        $msg = "You left yourself a message - $message";
                    } else {
                        $msg = "$sender left you a message - $message";
                    }

                    $event->addResponse(Response::msg($joiner, $msg));
                    unset($private[$joiner][$message]);
                }
            }
        });
    }

    /**
     * Help messages!
     */
    public function displayHelp(Event $event)
    {
        return "!msg <who> <msg> - Save a message for the specified user (send as a PM for the message to be delivered privately)";
    }

    /**
     * Return the name of the plugin.
     */
    public function getName()
    {
        return 'AnsweringMachinePlugin';
    }
}


<?php

namespace Oxygen\Data\Behaviour;

trait CacheInvalidator {

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $cacheInvalidationSettings;

    /**
     * Returns the cache invalidation settings.
     *
     * @return array
     */
    public function getCacheInvalidationSettings() {
        if($this->cacheInvalidationSettings == null) {
            $this->cacheInvalidationSettings = '[]';
        }
        return json_decode($this->cacheInvalidationSettings, true);
    }

    /**
     * Returns a list of the entities whose caches will be invalidated when this entity is updated.
     *
     * @return array
     */
    public function getEntitiesToBeInvalidated() {
        return $this->getCacheInvalidationSettings();
    }

    /**
     * Sets the cache invalidation settings.
     *
     * @param  array|string $settings
     * @return $this
     */
    public function setCacheInvalidationSettings($settings) {
        $this->cacheInvalidationSettings = is_string($settings) ? $settings : json_encode($settings, JSON_PRETTY_PRINT);
        return $this;
    }

    /**
     * Lets `$this` know that all the caches of `$object` should be invalidated whenever `$this` changes.
     *
     * @param \Oxygen\Data\Behaviour\PrimaryKeyInterface $object
     */
    public function addEntityToBeInvalidated(PrimaryKeyInterface $object) {
        $settings = $this->getCacheInvalidationSettings();
        $info = $this->getInfo($object);
        $settings[] = $info;

        // removes duplicates from the array
        $settings = array_intersect_key($settings, array_unique(array_map('serialize', $settings)));

        $this->setCacheInvalidationSettings($settings);
    }

    /**
     * Lets `$this` know that all the caches of `$object` should no longer be invalidated whenever `$this` changes.
     *
     * @param \Oxygen\Data\Behaviour\PrimaryKeyInterface $object
     */
    public function removeEntityToBeInvalidated(PrimaryKeyInterface $object) {
        $settings = $this->getCacheInvalidationSettings();
        $info = $this->getInfo($object);
        $settings = array_filter($settings, function($value) use($info) {
            return $value != $info;
        });
        $this->setCacheInvalidationSettings($settings);
    }

    private function getInfo(PrimaryKeyInterface $object) {
        return ['id' => $object->getId(), 'class' => get_class($object)];
    }

}