<?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\Console;

use Valkyrja\Application;
use Valkyrja\Console\Annotations\CommandAnnotations;
use Valkyrja\Console\Exceptions\CommandNotFound;
use Valkyrja\Console\Input\Input;
use Valkyrja\Console\Output\Output;
use Valkyrja\Support\Providers\ProvidersAwareTrait;
use Valkyrja\Support\Providers\Provides;

/**
 * Class Console.
 *
 * @author Melech Mizrachi
 */
class NativeConsole implements Console
{
    use ProvidersAwareTrait {
        register as traitRegister;
    }
    use Provides;

    /**
     * The run method to call within command handlers.
     */
    public const RUN_METHOD = 'run';

    /**
     * The application.
     *
     * @var \Valkyrja\Application
     */
    protected $app;

    /**
     * The commands.
     *
     * @var \Valkyrja\Console\Command[]
     */
    protected static $commands = [];

    /**
     * The command paths.
     *
     * @var string[]
     */
    protected static $paths = [];

    /**
     * The commands by name.
     *
     * @var string[]
     */
    protected static $namedCommands = [];

    /**
     * Whether the console has been setup.
     *
     * @var bool
     */
    protected static $setup = false;

    /**
     * Console constructor.
     *
     * @param Application $application The application
     */
    public function __construct(Application $application)
    {
        $this->app = $application;
    }

    /**
     * Add a new command.
     *
     * @param Command $command The command
     *
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function addCommand(Command $command): void
    {
        $command->setMethod($command->getMethod() ?? static::RUN_METHOD);
        $dispatcher = $this->app->dispatcher();

        $dispatcher->verifyClassMethod($command);
        $dispatcher->verifyFunction($command);
        $dispatcher->verifyClosure($command);

        $this->addParsedCommand(
            $command,
            $this->app->pathParser()->parse(
                $command->getPath()
            )
        );
    }

    /**
     * Add a parsed command.
     *
     * @param Command $command       The command
     * @param array   $parsedCommand The parsed command
     *
     * @return void
     */
    protected function addParsedCommand(Command $command, array $parsedCommand): void
    {
        // Set the properties
        $command->setRegex($parsedCommand['regex']);
        $command->setParams($parsedCommand['params']);
        $command->setSegments($parsedCommand['segments']);

        // Set the command in the commands list
        self::$commands[$command->getPath()] = $command;
        // Set the command in the commands paths list
        self::$paths[$command->getRegex()] = $command->getPath();

        // If the command has a name
        if (null !== $command->getName()) {
            // Set in the named commands list to find it more easily later
            self::$namedCommands[$command->getName()] = $command->getPath();
        }
    }

    /**
     * Get a command by name.
     *
     * @param string $name The command name
     *
     * @return \Valkyrja\Console\Command
     */
    public function command(string $name): ? Command
    {
        return $this->hasCommand($name)
            ? self::$commands[self::$namedCommands[$name]]
            : null;
    }

    /**
     * Determine whether a command exists by name.
     *
     * @param string $name The command name
     *
     * @return bool
     */
    public function hasCommand(string $name): bool
    {
        return isset(self::$namedCommands[$name]);
    }

    /**
     * Remove a command.
     *
     * @param string $name The command name
     *
     * @return void
     */
    public function removeCommand(string $name): void
    {
        if ($this->hasCommand($name)) {
            unset(
                self::$commands[self::$namedCommands[$name]],
                self::$namedCommands[$name]
            );
        }
    }

    /**
     * Get a command from an input.
     *
     * @param Input $input The input
     *
     * @throws \Valkyrja\Console\Exceptions\CommandNotFound
     *
     * @return \Valkyrja\Console\Command
     */
    public function inputCommand(Input $input): Command
    {
        return $this->matchCommand($input->getStringArguments());
    }

    /**
     * Match a command by path.
     *
     * @param string $path The path
     *
     * @throws \Valkyrja\Console\Exceptions\CommandNotFound
     *
     * @return \Valkyrja\Console\Command
     */
    public function matchCommand(string $path): Command
    {
        // If the path matches a set command path
        if (isset(self::$commands[$path])) {
            return self::$commands[$path];
        }

        $command = null;

        // Otherwise iterate through the commands and attempt to match via regex
        foreach (self::$paths as $regex => $commandPath) {
            // If the preg match is successful, we've found our command!
            if (preg_match($regex, $path, $matches)) {
                // Check if this command is provided
                if ($this->isProvided($commandPath)) {
                    // Initialize the provided command
                    $this->initializeProvided($commandPath);
                }

                // Clone the command to avoid changing the one set in the master
                // array
                $command = clone self::$commands[$commandPath];
                // The first match is the path itself
                unset($matches[0]);

                // Set the matches
                $command->setMatches($matches);

                return $command;
            }
        }

        // If a command was not found
        if (null === $command) {
            // Throw a not found exception
            throw new CommandNotFound('The command ' . $path . ' not found.');
        }

        return $command;
    }

    /**
     * Dispatch a command.
     *
     * @param Input  $input  The input
     * @param Output $output The output
     *
     * @throws \Valkyrja\Console\Exceptions\CommandNotFound
     *
     * @return mixed
     */
    public function dispatch(Input $input, Output $output)
    {
        // Verify the command exists
        if (null === $command = $this->inputCommand($input)) {
            throw new CommandNotFound('Specified command does not exists.');
        }

        if ($input->hasOption('-h') || $input->hasOption('--help')) {
            $command->setMethod('help');
        }

        if ($input->hasOption('-V') || $input->hasOption('--version')) {
            $command->setMethod('version');
        }

        return $this->dispatchCommand($command);
    }

    /**
     * Dispatch a command.
     *
     * @param Command $command The command
     *
     * @return mixed
     */
    public function dispatchCommand(Command $command)
    {
        // Trigger an event before dispatching
        $this->app->events()->trigger('Command.dispatching', [$command]);

        // Dispatch the command
        $dispatch = $this->app->dispatcher()->dispatchCallable(
            $command,
            $command->getMatches()
        );

        // Trigger an event after dispatching
        $this->app->events()->trigger(
            'Command.dispatched',
            [$command, $dispatch]
        );

        return $dispatch;
    }

    /**
     * Get all commands.
     *
     * @return \Valkyrja\Console\Command[]
     */
    public function all(): array
    {
        // Iterate through all the command providers to set any deferred
        // commands
        foreach (self::$provided as $provided => $provider) {
            // Initialize the provided command
            $this->initializeProvided($provided);
        }

        return self::$commands;
    }

    /**
     * Set the commands.
     *
     * @param Command[] $commands The commands
     *
     * @return void
     */
    public function set(Command ...$commands): void
    {
        self::$commands = $commands;
    }

    /**
     * Get the named commands list.
     *
     * @return array
     */
    public function getNamedCommands(): array
    {
        return self::$namedCommands;
    }

    /**
     * Setup the console.
     *
     * @param bool $force    [optional] Whether to force setup
     * @param bool $useCache [optional] Whether to use cache
     *
     * @throws \ReflectionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function setup(bool $force = false, bool $useCache = true): void
    {
        // If the console was already setup no need to do it again
        if (self::$setup && ! $force) {
            return;
        }

        // The console is setting up
        self::$setup = true;

        // If the application should use the console cache files
        if ($useCache && $this->app->config()['console']['useCache']) {
            $this->setupFromCache();

            // Then return out of setup
            return;
        }

        self::$paths         = [];
        self::$commands      = [];
        self::$namedCommands = [];

        // Setup command providers
        $this->setupCommandProviders();

        // If annotations are enabled and the events should use annotations
        if (
            $this->app->config()['console']['useAnnotations']
            && $this->app->config()['annotations']['enabled']
        ) {
            // Setup annotated event listeners
            $this->setupAnnotations();

            // If only annotations should be used
            if ($this->app->config()['console']['useAnnotationsExclusively']) {
                // Return to avoid loading events file
                return;
            }
        }

        // Include the events file
        require $this->app->config()['console']['filePath'];
    }

    /**
     * Setup the console from cache.
     *
     * @return void
     */
    protected function setupFromCache(): void
    {
        // Set the application console with said file
        $cache = $this->app->config()['cache']['console']
            ?? require $this->app->config()['console']['cacheFilePath'];

        self::$commands      = unserialize(
            base64_decode($cache['commands'], true),
            [
                'allowed_classes' => [
                    Command::class,
                ],
            ]
        );
        self::$paths         = $cache['paths'];
        self::$namedCommands = $cache['namedCommands'];
        self::$provided      = $cache['provided'];
    }

    /**
     * Setup command providers.
     *
     * @return void
     */
    protected function setupCommandProviders(): void
    {
        /** @var string[] $providers */
        $providers = $this->app->config()['console']['providers'];

        // Iterate through all the providers
        foreach ($providers as $provider) {
            $this->register($provider);
        }

        // If this is not a dev environment
        if (! $this->app->debug()) {
            return;
        }

        /** @var string[] $devProviders */
        $devProviders = $this->app->config()['console']['devProviders'];

        // Iterate through all the providers
        foreach ($devProviders as $provider) {
            $this->register($provider);
        }
    }

    /**
     * Register a provider.
     *
     * @param string $provider The provider
     * @param bool   $force    [optional] Whether to force regardless of
     *                         deferred status
     *
     * @return void
     */
    public function register(string $provider, bool $force = false): void
    {
        // Do the default registration of the service provider
        $this->traitRegister($provider, $force);

        /* @var \Valkyrja\Console\Support\CommandProvider $provider */
        // Get the commands names provided
        $commands = $provider::commands();

        // Iterate through the provided commands
        foreach ($provider::provides() as $key => $provided) {
            // Parse the provided path
            $parsedPath = $this->app->pathParser()->parse($provided);

            // Set the path and regex in the paths list
            self::$paths[$parsedPath['regex']] = $provided;
            // Set the path and command in the named commands list
            self::$namedCommands[$commands[$key]] = $provided;
        }
    }

    /**
     * Setup annotations.
     *
     * @throws \ReflectionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    protected function setupAnnotations(): void
    {
        /** @var CommandAnnotations $commandAnnotations */
        $commandAnnotations = $this->app->container()->getSingleton(
            CommandAnnotations::class
        );

        // Get all the annotated commands from the list of handlers
        $commands = $commandAnnotations->getCommands(
            ...$this->app->config()['console']['handlers']
        );

        // Iterate through the commands
        foreach ($commands as $command) {
            // Set the service
            $this->addCommand($command);
        }
    }

    /**
     * Get a cacheable representation of the commands.
     *
     * @throws \ReflectionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return array
     */
    public function getCacheable(): array
    {
        $this->setup(true, false);

        return [
            'commands'      => base64_encode(serialize(self::$commands)),
            'paths'         => self::$paths,
            'namedCommands' => self::$namedCommands,
            'provided'      => self::$provided,
        ];
    }

    /**
     * The items provided by this provider.
     *
     * @return array
     */
    public static function provides(): array
    {
        return [
            Console::class,
        ];
    }

    /**
     * Publish the provider.
     *
     * @param Application $app The application
     *
     * @return void
     */
    public static function publish(Application $app): void
    {
        $app->container()->singleton(
            Console::class,
            new static($app)
        );

        $app->console()->setup();
    }

    /**
     * Get the application.
     *
     * @return \Valkyrja\Application
     */
    protected function getApplication(): Application
    {
        return $this->app;
    }
}


<?php

namespace tourze\Http\Exception;

use tourze\Base\Exception\BaseException;
use tourze\Http\Http;

class Http305Exception extends ExpectedException
{

    /**
     * @var int
     */
    protected $_code = Http::USE_PROXY;

    /**
     * {@inheritdoc}
     */
    public function location($uri = null)
    {
        if (null === $uri)
        {
            return $this->headers('Location');
        }
        $this->headers('Location', $uri);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function check()
    {
        if (null === ($location = $this->headers('location')))
        {
            throw new BaseException("A 'location' must be specified for a redirect");
        }

        if (false === strpos($location, '://'))
        {
            throw new BaseException('An absolute URI to the proxy server must be specified');
        }

        return true;
    }

}


<?php

/*
 * This file is part of Core.
 *
 * (c) DraperStudio <hello@draperstudio.tech>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Apivore\Core;

use Apivore\Core\Contracts\HttpClient;
use Apivore\Core\Contracts\Request\ErrorHandler as RequestErrorHandler;
use Apivore\Core\Contracts\Response\ErrorHandler as ResponseErrorHandler;
use Apivore\Core\Contracts\Response\Normaliser;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Request;

/**
 * Class AbstractHttpClient.
 */
abstract class AbstractHttpClient implements HttpClient
{
    /**
     * @var array
     */
    protected $body = [];

    /**
     * @var array
     */
    protected $headers = [];

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

    /**
     * @var string
     */
    protected $responseFormat = 'json';

    /**
     * @var ErrorHandler
     */
    protected $requestErrorHandler;

    /**
     * @var ErrorHandler
     */
    protected $responseErrorHandler;

    /**
     * @var Normaliser
     */
    protected $responseNormaliser;

    /**
     * @var array
     */
    protected $requestModifiers = [];

    /**
     * @var array
     */
    protected $config;

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

    /**
     * AbstractHttpClient constructor.
     *
     * @param ErrorHandler $requestErrorHandler
     * @param ErrorHandler $responseErrorHandler
     * @param Normaliser   $responseNormaliser
     */
    public function __construct(
        RequestErrorHandler $requestErrorHandler,
        ResponseErrorHandler $responseErrorHandler,
        Normaliser $responseNormaliser)
    {
        $this->requestErrorHandler = $requestErrorHandler;
        $this->responseErrorHandler = $responseErrorHandler;
        $this->responseNormaliser = $responseNormaliser;
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function get($path)
    {
        return $this->request('GET', $path);
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function head($path)
    {
        return $this->request('HEAD', $path);
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function delete($path)
    {
        return $this->request('DELETE', $path);
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function put($path)
    {
        return $this->request('PUT', $path);
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function patch($path)
    {
        return $this->request('PATCH', $path);
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function post($path)
    {
        return $this->request('POST', $path);
    }

    /**
     * @param $path
     *
     * @return mixed
     */
    public function options($path)
    {
        return $this->request('OPTIONS', $path);
    }

    /**
     * @return GuzzleClient
     */
    public function getHttpClient()
    {
        $this->options['defaults']['headers'] = $this->getHeaders();

        if (class_exists($handler = $this->getHandler())) {
            $handler = new $handler($this);

            $this->setHandler($handler->create());
        }

        return new GuzzleClient($this->options);
    }

    /**
     * @param $baseUri
     * @param $path
     *
     * @return string
     */
    protected function buildRequestUri($baseUri, $path)
    {
        return $baseUri.$path;
    }

    protected function getHandler()
    {
    }

    /**
     * @param $method
     * @param $path
     *
     * @return mixed
     */
    private function request($method, $path)
    {
        $modifiedClient = $this->applyModifiers([
            'method'      => $method,
            'path'        => $path,
            'form_params' => $this->getFormParameters(),
            'multiplart'  => $this->getMultipart(),
            'query'       => $this->getQuery(),
            'json'        => $this->getJson(),
            'headers'     => $this->getHeaders(),
        ]);

        $modifiedClient->setHeaders($this->getHeaders());

        $client = $modifiedClient->getHttpClient();

        $request = new Request(
            $method,
            $this->buildRequestUri($modifiedClient->options['base_uri'], $path),
            $modifiedClient->headers
        );

        try {
            $response = $client->send($request, $modifiedClient->body);
        } catch (ClientException $e) {
            return $this->requestErrorHandler->handle($e);
        }

        return $modifiedClient->handleResponse($response->getBody());
    }

    /**
     * @param $response
     *
     * @return mixed
     */
    private function handleResponse($response)
    {
        $response = $this->responseNormaliser->normalise(
            $response, $this->responseFormat
        );

        $this->responseErrorHandler->handle($response);

        return $response;
    }

    /**
     * @param $arguments
     *
     * @return AbstractHttpClient
     */
    private function applyModifiers($arguments)
    {
        $modifiers = $this->getRequestModifier();

        $modifiedClient = $this;

        foreach ($modifiers as $modifier) {
            $modifier = new $modifier($modifiedClient, $arguments);

            $modifiedClient = $modifier->apply();
        }

        return $modifiedClient;
    }

    /**
     * @return mixed
     */
    public function getQuery()
    {
        return array_get($this->body, 'query');
    }

    /**
     * @param $data
     */
    public function setQuery($data)
    {
        $this->body['query'] = array_merge(
            array_get($this->body, 'query', []), $data
        );
    }

    /**
     * @param $key
     * @param $value
     */
    public function addQuery($key, $value)
    {
        $this->body['query'][$key] = $value;
    }

    public function flushQuery()
    {
        unset($this->body['query']);
    }

    /**
     * @return mixed
     */
    public function getFormParameters()
    {
        return array_get($this->body, 'form_params');
    }

    /**
     * @param $data
     */
    public function setFormParameters($data)
    {
        $this->body['form_params'] = array_merge(
            array_get($this->body, 'form_params', []), $data
        );
    }

    /**
     * @param $key
     * @param $value
     */
    public function addFormParameter($key, $value)
    {
        $this->body['form_params'][$key] = $value;
    }

    public function flushFormParameters()
    {
        unset($this->body['form_params']);
    }

    /**
     * @return mixed
     */
    public function getJson()
    {
        return array_get($this->body, 'json');
    }

    /**
     * @param $data
     */
    public function setJson($data)
    {
        $this->body['json'] = array_merge(
            array_get($this->body, 'json', []), $data
        );
    }

    /**
     * @param $key
     * @param $value
     */
    public function addJson($key, $value)
    {
        $this->body['json'][$key] = $value;
    }

    public function flushJson()
    {
        unset($this->body['json']);
    }

    /**
     * @return mixed
     */
    public function getMultipart()
    {
        return array_get($this->body, 'multipart');
    }

    /**
     * @param $data
     */
    public function setMultipart($data)
    {
        $this->body['multipart'] = array_merge(
            array_get($this->body, 'multipart', []), $data
        );
    }

    /**
     * @param $name
     * @param $contents
     */
    public function addMultipart($name, $contents)
    {
        $this->body['multipart'][] = compact('name', 'contents');
    }

    public function flushMultipart()
    {
        unset($this->body['multipart']);
    }

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

    /**
     * @param $headers
     */
    public function setHeaders($headers)
    {
        $this->headers = array_merge($this->headers, $headers);
    }

    /**
     * @param $key
     * @param $value
     */
    public function addHeader($key, $value)
    {
        $this->headers[$key] = $value;
    }

    public function flushHeaders()
    {
        unset($this->headers);
    }

    /**
     * @param $key
     *
     * @return mixed
     */
    public function getOption($key)
    {
        return $this->options[$key];
    }

    /**
     * @param $key
     * @param $value
     */
    public function setOption($key, $value)
    {
        $this->options[$key] = $value;
    }

    /**
     * @param $path
     */
    public function setBaseUrl($path)
    {
        $this->options['base_url'] = $path;
    }

    /**
     * @param $key
     * @param $value
     */
    public function setDefault($key, $value)
    {
        $this->options['defaults'][$key] = $value;
    }

    /**
     * @param $handler
     */
    public function setHandler($handler)
    {
        $this->options['handler'] = $handler;
    }

    /**
     * @param $modifier
     */
    public function addRequestModifier($modifier)
    {
        $this->requestModifiers[] = $modifier;
    }

    /**
     * @return array
     */
    public function getRequestModifier()
    {
        return $this->requestModifiers;
    }

    /**
     * @param $config
     */
    public function setConfig($config)
    {
        $this->config = $config;
    }

    /**
     * @param $key
     *
     * @return mixed
     */
    public function getConfig($key)
    {
        if (!empty($this->config) && !empty($key)) {
            return $this->config->$key;
        }

        return $this->config;
    }

    /**
     * @param $class
     */
    public function setApiClass($class)
    {
        $this->apiClass = $class;
    }

    /**
     * @return mixed
     */
    public function getApiClass()
    {
        return $this->apiClass;
    }
}


<?php

declare(strict_types=1);

namespace Ruwork\ObjectStore;

use Ruwork\ObjectStore\Exception\InvalidArgumentException;
use Ruwork\ObjectStore\Exception\UnexpectedValueException;
use Ruwork\ObjectStore\Normalizer\NormalizerInterface;
use Ruwork\ObjectStore\Normalizer\NullNormalizer;
use Ruwork\ObjectStore\Storage\StorageInterface;

final class Store implements StoreInterface
{
    private $class;
    private $storage;
    private $defaultFactory;
    private $normalizer;
    private $data;
    private $loaded = false;

    public function __construct(
        string $class,
        StorageInterface $storage,
        ?callable $defaultFactory = null,
        ?NormalizerInterface $normalizer = null
    ) {
        if (!class_exists($class)) {
            throw new InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
        }

        $this->class = $class;
        $this->storage = $storage;
        $this->defaultFactory = $defaultFactory
            ?? static function () {
                return null;
            };
        $this->normalizer = $normalizer ?? new NullNormalizer();
    }

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

    /**
     * {@inheritdoc}
     */
    public function get()
    {
        if ($this->loaded) {
            return $this->data;
        }

        $data = $this->storage->get();

        if (null === $data) {
            $data = ($this->defaultFactory)();
        } else {
            $data = $this->normalizer->denormalize($data, $this->class);
        }

        if (null !== $data && !$data instanceof $this->class) {
            throw UnexpectedValueException::createForValue($data, 'null or '.$this->class);
        }

        $this->loaded = true;

        return $this->data = $data;
    }

    /**
     * {@inheritdoc}
     */
    public function set($object): void
    {
        $this->data = $object;
        $this->loaded = true;
    }

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

        $normalized = $this->normalizer->normalize($this->data);
        $this->storage->set($normalized);
    }

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


<?php namespace Way\Generators\Commands;

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;

class ControllerGeneratorCommand extends GeneratorCommand {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'generate:controller';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generate a controller';

    /**
     * The path where the file will be created
     *
     * @return mixed
     */
    protected function getFileGenerationPath()
    {
        $path = $this->getPathByOptionOrConfig('path', 'controller_target_path');

        return $path. '/' . $this->argument('controllerName') . '.php';
    }

    /**
     * Fetch the template data
     *
     * @return array
     */
    protected function getTemplateData()
    {
        // LessonsController
        $name = ucwords($this->argument('controllerName'));

        // lessons
        $collection = strtolower(str_replace('Controller', '', $name));

        // lesson
        $resource = str_singular($collection);

        // Lesson
        $model = ucwords($resource);

        return compact('name', 'collection', 'resource', 'model');
    }

    /**
     * Get path to the template for the generator
     *
     * @return mixed
     */
    protected function getTemplatePath()
    {
        return $this->getPathByOptionOrConfig('templatePath', 'controller_template_path');
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['controllerName', InputArgument::REQUIRED, 'The name of the desired controller.']
        ];
    }

}


<?php

namespace Think\Model;

use Think\Model;

/**
 * 聚合模型
 */
abstract class MergeModel extends Model
{

    //  包含的模型列表 第一个必须是主表模型
    protected $modelList = [];
    //  主模型
    protected $masterModel = '';
    //  聚合模型的查询JOIN类型
    protected $joinType = 'INNER';
    //  外键名 默认为主表名_id
    protected $fk = '';
    //  需要处理的模型映射字段，避免混淆 array( id => 'user.id'  )
    protected $mapFields = [];

    /**
     * 取得DB类的实例对象 字段检查

     *
     * @param string $name 模型名称
     */
    public function __construct($name = '')
    {
        parent::__construct($name);
        // 聚合模型的字段信息
        if (empty($this->fields) && !empty($this->modelList)) {
            $fields = [];
            foreach ($this->modelList as $model) {
                // 获取模型的字段信息
                $result = $this->db->getFields(D($model)->getTableName());
                $_fields = array_keys($result);
                // $this->mapFields  =   array_intersect($fields,$_fields);
                $fields = array_merge($fields, $_fields);
            }
            $this->fields = $fields;
        }

        // 设置第一个模型为主表模型
        if (empty($this->masterModel) && !empty($this->modelList)) {
            $this->masterModel = $this->modelList[0];
        }
        // 主表的主键名
        $this->pk = D($this->masterModel)->getPk();

        // 设置默认外键名 仅支持单一外键
        if (empty($this->fk)) {
            $this->fk = strtolower($this->masterModel).'_id';
        }

    }

    /**
     * 得到完整的数据表名

     * @return string
     */
    public function getTableName()
    {
        if (empty($this->trueTableName)) {
            $tableName = [];
            $models = $this->modelList;
            foreach ($models as $model) {
                $tableName[] = D($model)->getTableName().' '.$model;
            }
            $this->trueTableName = implode(',', $tableName);
        }

        return $this->trueTableName;
    }

    /**
     * 自动检测数据表信息

     * @return void
     */
    protected function _checkTableInfo()
    {
    }

    /**
     * 新增聚合数据

     *
     * @param mixed $data 数据
     * @param array $options 表达式
     * @param bool $replace 是否replace
     *
     * @return mixed
     */
    public function add($data = '', $options = [], $replace = false)
    {
        if (empty($data)) {
            // 没有传递数据，获取当前数据对象的值
            if (!empty($this->data)) {
                $data = $this->data;
                // 重置数据
                $this->data = [];
            } else {
                $this->error = L('_DATA_TYPE_INVALID_');

                return false;
            }
        }
        // 启动事务
        $this->startTrans();
        // 写入主表数据
        $result = D($this->masterModel)->strict(false)->add($data);
        if ($result) {
            // 写入外键数据
            $data[$this->fk] = $result;
            $models = $this->modelList;
            array_shift($models);
            // 写入附表数据
            foreach ($models as $model) {
                $res = D($model)->strict(false)->add($data);
                if (!$res) {
                    $this->rollback();

                    return false;
                }
            }
            // 提交事务
            $this->commit();
        } else {
            $this->rollback();

            return false;
        }

        return $result;
    }

    /**
     * 对保存到数据库的数据进行处理

     *
     * @param mixed $data 要操作的数据
     *
     * @return bool
     */
    protected function _facade($data)
    {

        // 检查数据字段合法性
        if (!empty($this->fields)) {
            if (!empty($this->options['field'])) {
                $fields = $this->options['field'];
                unset($this->options['field']);
                if (is_string($fields)) {
                    $fields = explode(',', $fields);
                }
            } else {
                $fields = $this->fields;
            }
            foreach ($data as $key => $val) {
                if (!in_array($key, $fields, true)) {
                    unset($data[$key]);
                } elseif (array_key_exists($key, $this->mapFields)) {
                    // 需要处理映射字段
                    $data[$this->mapFields[$key]] = $val;
                    unset($data[$key]);
                }
            }
        }

        // 安全过滤
        if (!empty($this->options['filter'])) {
            $data = array_map($this->options['filter'], $data);
            unset($this->options['filter']);
        }
        $this->_before_write($data);

        return $data;
    }

    /**
     * 保存聚合模型数据

     *
     * @param mixed $data 数据
     * @param array $options 表达式
     *
     * @return bool
     */
    public function save($data = '', $options = [])
    {
        // 根据主表的主键更新
        if (empty($data)) {
            // 没有传递数据，获取当前数据对象的值
            if (!empty($this->data)) {
                $data = $this->data;
                // 重置数据
                $this->data = [];
            } else {
                $this->error = L('_DATA_TYPE_INVALID_');

                return false;
            }
        }
        if (empty($data)) {
            // 没有数据则不执行
            $this->error = L('_DATA_TYPE_INVALID_');

            return false;
        }
        // 如果存在主键数据 则自动作为更新条件
        $pk = $this->pk;
        if (isset($data[$pk])) {
            $where[$pk] = $data[$pk];
            $options['where'] = $where;
            unset($data[$pk]);
        }
        $options['join'] = '';
        $options = $this->_parseOptions($options);
        // 更新操作不使用JOIN
        $options['table'] = $this->getTableName();

        if (is_array($options['where']) && isset($options['where'][$pk])) {
            $pkValue = $options['where'][$pk];
        }
        if (false === $this->_before_update($data, $options)) {
            return false;
        }
        $result = $this->db->update($data, $options);
        if (false !== $result) {
            if (isset($pkValue)) {
                $data[$pk] = $pkValue;
            }
            $this->_after_update($data, $options);
        }

        return $result;
    }

    /**
     * 删除聚合模型数据
     * @access       public
     * @noinspection PhpSignatureMismatchDuringInheritanceInspection
     *
     * @param mixed $options 表达式
     *
     * @return mixed
     */
    public function delete($options)
    {
        $pk = $this->pk;
        if (empty($options) && empty($this->options['where'])) {
            // 如果删除条件为空 则删除当前数据对象所对应的记录
            if (!empty($this->data) && isset($this->data[$pk])) {
                return $this->delete($this->data[$pk]);
            } else {
                return false;
            }
        }

        if (is_numeric($options) || is_string($options)) {
            // 根据主键删除记录
            if (strpos($options, ',')) {
                $where[$pk] = ['IN', $options];
            } else {
                $where[$pk] = $options;
            }
            $options = [];
            $options['where'] = $where;
        }
        // 分析表达式
        $options['join'] = '';
        $options = $this->_parseOptions($options);
        if (empty($options['where'])) {
            // 如果条件为空 不进行删除操作 除非设置 1=1
            return false;
        }
        if (is_array($options['where']) && isset($options['where'][$pk])) {
            $pkValue = $options['where'][$pk];
        }

        $options['table'] = implode(',', $this->modelList);
        $options['using'] = $this->getTableName();
        if (false === $this->_before_delete($options)) {
            return false;
        }
        $result = $this->db->delete($options);
        if (false !== $result) {
            $data = [];
            if (isset($pkValue)) {
                $data[$pk] = $pkValue;
            }
            $this->_after_delete($data, $options);
        }

        // 返回删除记录个数
        return $result;
    }

    /**
     * 表达式过滤方法

     *
     * @param string $options 表达式
     *
     * @return void
     */
    protected function _options_filter(&$options)
    {
        if (!isset($options['join'])) {
            $models = $this->modelList;
            array_shift($models);
            foreach ($models as $model) {
                $options['join'][] = $this->joinType.' JOIN '.D($model)->getTableName(
                    ).' '.$model.' ON '.$this->masterModel.'.'.$this->pk.' = '.$model.'.'.$this->fk;
            }
        }
        $options['table'] = D($this->masterModel)->getTableName().' '.$this->masterModel;
        $options['field'] = $this->checkFields(isset($options['field']) ? $options['field'] : '');
        if (isset($options['group'])) {
            $options['group'] = $this->checkGroup($options['group']);
        }
        if (isset($options['where'])) {
            $options['where'] = $this->checkCondition($options['where']);
        }
        if (isset($options['order'])) {
            $options['order'] = $this->checkOrder($options['order']);
        }
    }

    /**
     * 检查条件中的聚合字段

     *
     * @param array $where
     *
     * @return array
     * @internal param mixed $data 条件表达式
     */
    protected function checkCondition(array $where = [])
    {
        if (is_array($where)) {
            $view = [];
            foreach ($where as $name => $value) {
                if (array_key_exists($name, $this->mapFields)) {
                    // 需要处理映射字段
                    $view[$this->mapFields[$name]] = $value;
                    unset($where[$name]);
                }
            }
            $where = array_merge($where, $view);
        }

        return $where;
    }

    /**
     * 检查Order表达式中的聚合字段

     *
     * @param string $order 字段
     *
     * @return string
     */
    protected function checkOrder($order = '')
    {
        if (is_string($order) && !empty($order)) {
            $orders = explode(',', $order);
            $_order = [];
            foreach ($orders as $order) {
                $array = explode(' ', trim($order));
                $field = $array[0];
                $sort = isset($array[1]) ? $array[1] : 'ASC';
                if (array_key_exists($field, $this->mapFields)) {
                    // 需要处理映射字段
                    $field = $this->mapFields[$field];
                }
                $_order[] = $field.' '.$sort;
            }
            $order = implode(',', $_order);
        }

        return $order;
    }

    /**
     * 检查Group表达式中的聚合字段

     *
     * @param string $group 字段
     *
     * @return string
     */
    protected function checkGroup($group = '')
    {
        if (!empty($group)) {
            $groups = explode(',', $group);
            $_group = [];
            foreach ($groups as $field) {
                // 解析成聚合字段
                if (array_key_exists($field, $this->mapFields)) {
                    // 需要处理映射字段
                    $field = $this->mapFields[$field];
                }
                $_group[] = $field;
            }
            $group = implode(',', $_group);
        }

        return $group;
    }

    /**
     * 检查fields表达式中的聚合字段

     *
     * @param string $fields 字段
     *
     * @return string
     */
    protected function checkFields($fields = '')
    {
        if (empty($fields) || '*' == $fields) {
            // 获取全部聚合字段
            $fields = $this->fields;
        }
        if (!is_array($fields)) {
            $fields = explode(',', $fields);
        }

        // 解析成聚合字段
        $array = [];
        foreach ($fields as $field) {
            if (array_key_exists($field, $this->mapFields)) {
                // 需要处理映射字段
                $array[] = $this->mapFields[$field].' AS '.$field;
            } else {
                $array[] = $field;
            }
        }

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

    /**
     * 获取数据表字段信息

     * @return array
     */
    public function getDbFields()
    {
        return $this->fields;
    }

}


<?php

namespace Reaction\Console;

use Reaction;
use Reaction\Console\Controllers\HelpController;
use Reaction\RequestApplicationInterface;

/**
 * UnknownCommandException represents an exception caused by incorrect usage of a console command.
 */
class UnknownCommandException extends Exception
{
    /**
     * @var string the name of the command that could not be recognized.
     */
    public $command;

    /**
     * @var RequestApplicationInterface
     */
    protected $app;

    /**
     * Construct the exception.
     *
     * @param string $route the route of the command that could not be found.
     * @param RequestApplicationInterface $application the console application instance involved.
     * @param int $code the Exception code.
     * @param \Exception $previous the previous exception used for the exception chaining.
     */
    public function __construct($route, $application, $code = 0, \Exception $previous = null)
    {
        $this->command = $route;
        $this->app = $application;
        parent::__construct("Unknown command \"$route\".", $code, $previous);
    }

    /**
     * @return string the user-friendly name of this exception
     */
    public function getName()
    {
        return 'Unknown command';
    }

    /**
     * Suggest alternative commands for [[$command]] based on string similarity.
     *
     * Alternatives are searched using the following steps:
     *
     * - suggest alternatives that begin with `$command`
     * - find typos by calculating the Levenshtein distance between the unknown command and all
     *   available commands. The Levenshtein distance is defined as the minimal number of
     *   characters you have to replace, insert or delete to transform str1 into str2.
     *
     * @see http://php.net/manual/en/function.levenshtein.php
     * @return array a list of suggested alternatives sorted by similarity.
     */
    public function getSuggestedAlternatives()
    {
        $help = Reaction::$app->router->createController('help', ['app' => $this->app], false);
        if ($help[0] === null || $this->command === '') {
            return [];
        }
        /** @var $helpController HelpController */
        list($helpController, $actionID) = $help;

        $availableActions = [];
        foreach ($helpController->getCommands() as $command) {
            $result = Reaction::$app->router->createController($command, ['app' => $this->app], false);
            if ($result[0] === null) {
                continue;
            }
            // add the command itself (default action)
            $availableActions[] = $command;

            // add all actions of this controller
            /** @var $controller Reaction\Console\Routes\Controller */
            list($controller, $actionID) = $result;
            $actions = $helpController->getActions($controller);
            if (!empty($actions)) {
                $prefix = $controller->getUniqueId();
                foreach ($actions as $action) {
                    $availableActions[] = $prefix . '/' . $action;
                }
            }
        }

        return $this->filterBySimilarity($availableActions, $this->command);
    }

    /**
     * Find suggest alternative commands based on string similarity.
     *
     * Alternatives are searched using the following steps:
     *
     * - suggest alternatives that begin with `$command`
     * - find typos by calculating the Levenshtein distance between the unknown command and all
     *   available commands. The Levenshtein distance is defined as the minimal number of
     *   characters you have to replace, insert or delete to transform str1 into str2.
     *
     * @see http://php.net/manual/en/function.levenshtein.php
     * @param array $actions available command names.
     * @param string $command the command to compare to.
     * @return array a list of suggested alternatives sorted by similarity.
     */
    private function filterBySimilarity($actions, $command)
    {
        $alternatives = [];

        // suggest alternatives that begin with $command first
        foreach ($actions as $action) {
            if (strpos($action, $command) === 0) {
                $alternatives[] = $action;
            }
        }

        // calculate the Levenshtein distance between the unknown command and all available commands.
        $distances = array_map(function($action) use ($command) {
            $action = strlen($action) > 255 ? substr($action, 0, 255) : $action;
            $command = strlen($command) > 255 ? substr($command, 0, 255) : $command;
            return levenshtein($action, $command);
        }, array_combine($actions, $actions));

        // we assume a typo if the Levenshtein distance is no more than 3, i.e. 3 replacements needed
        $relevantTypos = array_filter($distances, function($distance) {
            return $distance <= 3;
        });
        asort($relevantTypos);
        $alternatives = array_merge($alternatives, array_flip($relevantTypos));

        return array_unique($alternatives);
    }
}


<?php
namespace Li;
/**
 * 文件上传
 */
class File
{
    public static $_instance;
    private $_config=array(
        'autoFolder'=>true,     // 自动创建目录
        'savePath'=>'',         // 存储目录
    );

    protected $_file;

    static public function file()
    {
        if(!(self::$_instance instanceof self))
        {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    /**
     * 上传文件
     * array(1) {
     *   ["file"]=>
     *   array(5) {
     *     ["name"]=>
     *     string(44) "a71ea8d3fd1f413458fdd278261f95cad0c85eea.jpg"
     *     ["type"]=>
     *     string(10) "image/jpeg"
     *     ["tmp_name"]=>
     *     string(24) "F:\xampp\tmp\phpBA0B.tmp"
     *     ["error"]=>
     *     int(0)
     *     ["size"]=>
     *     int(11345)
     *   }
     * }
     */
    public function upload($file)
    {
        $fileName = $this->getFileName($file);
        $filePath = $this->getFilePath($file);

        $this->_file=$file;
        // move_uploaded_file(filename, destination);
    }
    public function uploads($file)
    {
        $fileName = $this->getFileName($file);
        $filePath = $this->getFilePath($file);

        $this->_file=$file;
        // move_uploaded_file(filename, destination);
    }

    public function getFileName($file)
    {
        return md5($file['tmp_name']);
    }

    public function getFilePath($file)
    {
        $path='';

        return $path;
    }

    public function remote($url)
    {

    }

    /**
     * 写文件
     */
    public static function write($file,$content,$recursive=false,$mode=0777)
    {
        $dir = dirname($file);

        if(!is_dir($dir))
        {
            mkdir($dir,$mode,$recursive);
        }

        return file_put_contents($file,$content);
    }

    public static function read()
    {

    }

}


<?php
/**
 * This file is part of the CarteBlanche PHP framework.
 *
 * (c) Pierre Cassat <me@e-piwi.fr> and contributors
 *
 * License Apache-2.0 <http://github.com/php-carteblanche/carteblanche/blob/master/LICENSE>
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CarteBlanche\App\ConfigFiletype;

use \CarteBlanche\Interfaces\ConfigFiletypeInterface;
use \Library\Converter\Xml2Array;

/**
 * @author  Piero Wbmstr <me@e-piwi.fr>
 */
class XmlFiletype implements ConfigFiletypeInterface
{

    /**
     * Parse a file content
     *
     * @param   \SplFileInfo    $file   The file to parse
     * @return array
     */
    function parse(\SplFileInfo $file)
    {
        $content = file_get_contents($file->getRealpath());
        libxml_clear_errors();
        $xml_config = Xml2Array::convert($content);
        $config = $this->rebuild($xml_config);

/*
        libxml_clear_errors();
        libxml_use_internal_errors(true);
        try {
            $xml = new \SimpleXMLElement($content);
        } catch (\Exception $e) {
            $errors = libxml_get_errors();
            if (!empty($errors)) {
                $last_error = libxml_get_last_error();
                throw new \RuntimeException(
                    sprintf('XML parsing errors with message "%s"!', $last_error->message)
                );
            }
        }
var_export($xml);
        $config = array();

        libxml_clear_errors();
*/

        return $config;
    }

    public function rebuild($xml)
    {
        $array = array();
        foreach ($xml as $index=>$element) {
            if ($index==='@root') {
                continue;
            }
            if (is_array($element) && !isset($element['@content'])) {
                if (in_array($index, array('item', 'items'))) {
                    return $this->rebuild($element);
                } else {
                    $array[$index] = $this->rebuild($element);
                }
            } else {
                $index_rebuilt = $index;
                $content_rebuilt = null;
                if (is_array($element)) {
                    if (isset($element['@attributes']) && isset($element['@attributes']['id'])) {
                        $index_rebuilt = $element['@attributes']['id'];
                    }
                    if (isset($element['@content'])) {
                        $content_rebuilt = $element['@content'];
                    }
                } else {
                    $content_rebuilt = $element;
                }
                $array[$index_rebuilt] = $content_rebuilt;
            }
        }
        return $array;
    }

}

// Endfile


<?php
/**
 * 模板编译类,涉及元语言操作
 */
declare(strict_types=1);

namespace icePHP;

/*  模板语法

  模板语法
    定界符: {  }
    注意:不要有多余的空白
    同一行内避免 两个 标签,有可能会出错.

    模板中可使用以下语法 ,注意:不要有多余的空白
    {xml}
        输出XML文件头: <?xml version="1.0" encoding="utf-8" >
    {upload}
        输出上传文件根路径,通常是/upload/
    for循环
        {for($i=0;$i<count($arr);$i++)}
        {endfor} 或者 {/for}
    foreach循环
        {foreach($arr as $k=>$v)}
        {endforeach} 或者 {/foreach}
    while循环
        {while(条件表达式)}
        {endwhile} 或者 {/while}
    if判断
        {if($i>5)}
        {elseif($i>2)}
        {else}
        {endif} 或者 {/if}
    判断是否是IE浏览器
        {ifIE}
        {else}
        {endif}/{/if}
    赋值
        {assign($变量=表达式)}
        或
        {let($变量=表达式)}
    嵌套原生代码
        {php}
        {/php}
    子模板包含
    	{include('子模板',参数数组)}
    {js(脚本1,脚本2,...)}
        包含一组JS文件
    {css(样式文件1,样式文件2,...)}
        引入一组样式文件
    当变量不存在,或数组元素不存在时,显示默认值
      	{default($变量/$数组元素,'<默认值>')}
    如果存在则显示
        {?<变量或表达式>}
    显示变量的值
        {$变量}
        {$对象->属性}
        {$数组[下标]}
    模板注释
    	{#注释内容}
        {*注释内容*}
    显示函数返回值
        {date('Y-m-d H;i:s')}
        注意:既然是函数调用,最后就应该是')'结束
        例:{dump($var,'title',true)}
        这将显示一个复杂变量的值,此函数请参考相应方法
        {MUser::getName($id)}
        也可以调用Model的静态方法
    多语言翻译
        {_('文本内容')}
        这将调用全局函数translation()
    定义在viewUnit中的静态方法
        使用方法   {:方法名(参数表)}
        back($url, $title = ''):
            显示一个返回列表的按钮
        add($url, $title = ''):
            显示一个新增按钮
        crumb(array $list):
            显示面包屑,带任意多的参数,格式为名称=>地址 或 单独名称
        date($config):
            显示一个日期控件
        discount($now, $old, $pres = 1):
            计算折扣
        money($money):
            美化金额
        pathCss():
            输出样式文件根路径,通常是/static/css/ 此功能也可简写为{css}
        pathJs():
            输出脚本文件根路径,通常是/static/js/ 此功能也可简写为{js}
        pathStatic():
            输出静态资源所在路径,通常是/static/
        pathImg()
            输出图片文件根路径,通常是 /static/images/,或/static/<当前模块>/images/ 此功能也可简写为{img}
        pathRoot()
            返回根路径,去除最后的斜线
        phone($phone)
            隐藏手机号码中间三位
        seo($page, array $params = [])
            生成SEO相关内容
        short($v, $len = 20)
            对超长字符串进行截断
        string(array $config)
            显示一个不可编辑的文本
        submit($title = '提交')
            显示一个表单提交按钮
        table(array $config)
            显示一个不可编辑的表格
        ver()
            获取当前版本号
        定义的全局函数
        可以使用 {函数名(参数表)} 的形式调用全部PHP全局函数,包括框架global中定义的全局函数
        以下举例常用的全局函数
        dump($vars, $label = '', $return = false)
            显示变量内容,用于调试
        fragment($m, $c, $a, array $params = [], $cached = 7200)
            显示一个页面片段, 关于页面片段是一个专题
        left / mid / datetime/ today/json/urlAppend
            请参考相应函数
 */

final class Template
{
    //禁止实例化
    private function __construct()
    {
    }

    // 临时记录当前请求的模块名称,免得总是计算
    private static $module;

    public static function setModule(string $module): void
    {
        self::$module = $module;
    }

    //记录当前控制器
    private static $controller;

    public static function setController(string $controller): void
    {
        self::$controller = $controller;
    }

    //记录当前动作
    private static $action;

    public static function setAction(string $action): void
    {
        self::$action = $action;
    }

    //记录全部模块名称
    private static $modules;

    public static function setModules(array $modules): void
    {
        self::$modules = $modules;
    }

    //系统根目录
    private static $root;

    public static function setRoot(string $roor): void
    {
        self::$root = $roor;
    }

    /**
     * 显示视图,如果需要,智能编译
     *
     * @param string $view 视图名称
     *            格式一: <c>/ 这将匹配 当前模块的 view/<c>/.tpl 或
     *            /module/common/view/...
     *            格式二:  使用当前控制器名称,并按格式一处理
     *            格式三: null 使用当前动作名称,并按格式二处理
     * @param array $params 参数数组,数组中的键值对将被释放为变量
     * @return string
     * @throws TemplateException
     */
    static public function display(string $view = null, array $params = []): string
    {
        // 是否是被嵌套,默认是否
        static $nested = false;

        // 如果有模板变量,解开
        if ($params and count($params)) {
            extract($params);
        }

        // 如果是被嵌套,直接包含就是了
        if ($nested) {
            include(self::getTpl($view));
            return '';
        }

        // 当前是被嵌套
        $nested = 1;

        // 引入模版文件
        include(self::getTpl($view));

        // 嵌套结束了
        $nested = 0;

        return '';
    }

    /**
     * 检查所有模板文件,如需要,则编译
     */
    static public function recompile(): void
    {
        //输出显示头
        echo '
';

        //逐个模板进行重编译
        foreach (self::$modules as $module) {
            // 重新编译所有视图
            self::recompileFolder($module, '');
        }

        // 清除所有页面缓存
        CacheFactory::instance('Page')->clearAll();

        //输出显示尾
        echo 'Clear Cache

';
    }

    /**
     * 编译一个目录,如果有子目录,则递归
     * @param string $module 模块名
     * @param string $folder 目录名,相对于模板根路径 ,初始为'',之后 为'admin/',之类
     * @return int 调用层数
     */
    static private function recompileFolder(string $module, string $folder): int
    {
        //本模块模板文件目录
        $path = self::$root . 'program/module/' . $module . '/view/' . $folder;

        //如果目录不存在
        if (!is_dir($path) or $folder == 'add/table') {
            return 0;
        }

        // 本目录及子目录重新编译的模板计数
        $compiled = 0;

        //遍历目录
        $root = new \DirectoryIterator($path);
        foreach ($root as $f) {
            // 不处理 . .. *.svn
            if ($f->isDot() or $f->getFilename() == '.svn') {
                continue;
            }

            //遍历子目录
            if ($f->isDir()) {
                $compiled += self::recompileFolder($module, trim($folder . '/' . $f->getFilename(), '/'));
                continue;
            }

            //只编译tpl后缀的文件
            if ($f->getExtension() != 'tpl') {
                continue;
            }

            //模板文件名
            $filename = $f->getFilename();

            //编译前文件名,编译后文件名
            $source = $path . '/' . $filename;
            $target = str_replace('/program/module/', '/run/view_c/', $path);

            echo 'Recompiled ' . $source . '
';

            //编译一个模板文件
            $compiled += self::compile($source, $target);
        }

        //返回编译了多少个模板
        return $compiled;
    }

    /**
     * 根据视图获取编译后的模板文件,会自动进行智能编译
     *
     * @param string $view
     *            格式一: <c>/ 这将匹配 当前模块的 view/<c>/.tpl 或
     *            /module/common/view/...
     *            格式二:  使用当前控制器名称,并按格式一处理
     *            格式三: null 使用当前动作名称,并按格式二处理
     *
     * @return string 编译后的模板文件(带路径)
     * @throws TemplateException
     */
    static private function getTpl(?string $view = null): string
    {

        // 如果未提供视图文件名,则根据当前模块,控制器和当前动作生成
        if (!$view) {
            $view = self::$controller . '/' . self::$action;
        } elseif (strpos($view, '/') === false) {
            // 替换路径分隔符
            $view = str_replace('\\', '/', trim($view, '/'));

            // 只提供了动作名称,则附加当前控制器名称
            $view = self::$controller . '/' . $view;
        }

        //当前模块
        $m = self::$module;

        // 如果当前模块有此视图,则使用当前模块的
        if ($m and self::checkFile(self::$root . 'program/module/' . $m . '/view/' . $view . '.tpl')) {
            // 编译前的源文件
            $source = self::$root . 'program/module/' . $m . '/view/' . $view . '.tpl';

            // 编译后的目标文件
            $compiled = self::$root . 'run/view_c/' . $m . '/' . $view . '.php';
        } elseif (self::checkFile(self::$root . 'program/view/' . $view . '.tpl')) {
            // 编译前的源文件
            $source = self::$root . 'program/view/' . $view . '.tpl';

            // 编译后的目标文件
            $compiled = self::$root . 'run/view_c/' . $view . '.php';
        } else {
            // 实在没找到
            throw new TemplateException('模板文件不存在:' . $view, TemplateException::TEMPLATE_NOT_FOUND);
        }

        // 如果关闭了模板自动编译的开关,则不自动编译,这个太不常用了.
        if (configDefault(true, 'system', 'template')) {
            self::compile($source, $compiled);
        }

        // 编译后的模板应该是这个名字
        return $compiled;
    }

    /**
     * 预编译第一步
     *
     * @param $source string 模板源文件名
     * @param $target string         编译后的文件名
     * @return int 是否确实重新编译了模板文件 1/0
     */
    static private function compile(string $source, string $target): int
    {
        // 目标文件已经存在,且文件日期要晚于源文件,则不用重新编译
        if (file_exists($target) and filemtime($source) <= filemtime($target)) {
            return 0;
        }

        // 重新编译
        $content = self::compile2($source);

        // 创建目标目录
        makeDir(dirname($target));

        // 写入编译后的视图
        write($target, $content, LOCK_EX);

        return 1;
    }

    /**
     * 预编译模板第二步
     *
     * @param string $view
     * @return string todo (.*?) /U非贪婪模式
     */
    static private function compile2(string $view): string
    {
        // 取模板内容
        $source = file_get_contents($view);

        // 短的开始标识
        $s = '<' . '?';

        // 长的开始标识
        $b = $s . 'php ';

        // 结束标识
        $e = '?' . '>';

        // 不允许出现PHP代码
        if (strpos($source, $s) !== false) {
            trigger_error('模板中不允许直接使用PHP:' . $view, E_USER_ERROR);
        }

        // 要替换 的内容
        $replace = [
            // 1.XML头
            '/{xml}/U' => $s . 'xml version="1.0" encoding="utf-8"' . $e,

            // 2.图片根路径
            '/{img}/U' => self::pathImg(),

            // 3.上传文件根路径
            '/{upload}/U' => self::pathUpload(),

            // 4.样式文件根路径
            '/{css}/U' => self::pathCss(),

            // 5.脚本文件根路径
            '/{js}/U' => self::pathJs(),

            // 6.通用,视图单元类的静态方法,便于开发者扩展
            '/{:(.*?\))}/U' => $s . '=icePHP\ViewUnit::\1' . $e,

            // 7.判断IE浏览器
            '/{ifIE}/' => $b . 'if(' . (self::isIE() ? '1' : '0') . '):' . $e,

            // 8.匹配 for 循环开始,与PHP语法一致
            '/{for\b(\(.*?\))}/U' => $b . 'for\1:' . $e,

            // 9.匹配 for 循环结束
            '/{endfor}/' => $b . 'endfor' . $e,
            '/{\/for}/' => $b . 'endfor' . $e,

            // 10.匹配 foreach 循环开始,与PHP语法一致
            '/{foreach\b(\(.*?\))}/U' => $b . 'foreach\1:' . $e,

            // 11.匹配 foreach 循环结束
            '/{endforeach}/' => $b . 'endforeach' . $e,
            '/{\/foreach}/' => $b . 'endforeach' . $e,

            // 12.匹配 while 循环开始
            '/{while\b(\(.*?\))}/U' => $b . 'while\1:' . $e,

            // 13.匹配 while 循环结束
            '/{endwhile}/' => $b . 'endwhile' . $e,
            '/{\/while}/' => $b . 'endwhile' . $e,

            // 14.匹配 if 开始,与PHP语法一致
            '/{if\b(\(.*?\))}/U' => $b . 'if\1:' . $e,

            // 15.匹配 elseif,与PHP语法一致
            '/{elseif\b(\(.*?\))}/U' => $b . 'elseif\1:' . $e,

            // 16.匹配 else
            '/{else}/' => $b . 'else:' . $e,

            // 17.匹配 if 结束
            '/{endif}/' => $b . 'endif' . $e,
            '/{\/if}/' => $b . 'endif' . $e,

            // 18.匹配 赋值 {assign($var=exp)}
            '/{assign\b\((.*?)\)}/U' => $b . '\1' . $e,
            '/{let\b\((.*?)\)}/U' => $b . '\1' . $e,

            // 19.匹配 原生代码开始
            '/{php}/' => $b,

            // 20.匹配 原生代码结束
            '/{\/php}/' => $e,

            // 21.匹配 子模板包含,语法 {include('子模板',参数数组)}
            '/{include\b(\(.*?\))}/U' => $b . 'icePHP\display\1' . $e,

            // 22.包含JS文件
            '/{js(\(.*?\))}/U' => $s . '=icePHP\Template::js\1' . $e,

            // 23.包含CSS文件
            '/{css(\(.*?\))}/U' => $s . '=icePHP\Template::css\1' . $e,

            // 24.default 默认值
            '/{default\((\$[^\,]*?)\,([^\)]*?)\)}/U' => "{$s}=!empty(\\1)?\\1:\\2{$e}",

            // 25. {?$xxx} 如果存在则显示
            '/{\?(\$.*?)}/U' => $s . '=(isset(\1) and \1!=="0000-00-00")?\1:""' . $e,

            // 26. 匹配 显示变量的值,与PHP语法一致
            '/{\$([^}]*?)}/U' => $s . '=$\1' . $e,

            // 27. 注释,不被编译
            '/{#[^}]*?}/U' => '',
            '/{\*[^(\*})]*?\*}/U' => '',

            // 对 url进行特殊处理
            '/{url(\(.*?\))}/U' => $s . '=icePHP\url\1' . $e,

            // 28. 匹配 显示函数返回值,与PHP语法一致
            '/{(.*?\))}/U' => $s . '=\1' . $e,

            //29.多语言翻译
            '/{\_\((.*?\))}/U' => $s . '=icePHP\translation(\1)' . $e,
        ];

        // 替换
        $target = preg_replace(array_keys($replace), array_values($replace), $source);

        // 如果不是调试模式,则去除首尾空格,去除HTML注释
        if (!isDebug()) {
            $target = preg_replace('/^\s*|\s*$/m', '', $target);
            $target = preg_replace('/<\!\-\-.*-->/m', '', $target);
        }

        // 返回编译结果
        return $target;
    }

    /**
     * 返回根路径,去除最后的斜线
     */
    static public function pathRoot(): string
    {
        return rtrim(configDefault('/', 'system', 'host'), '/\\');
    }

    /**
     * 获取本模块资源 文件目录
     */
    static private function pathModule(): string
    {
        return self::pathRoot() . '/static/' . (self::$module ? self::$module . '/' : '');
    }

    /**
     * 获取图片文件根路径
     */
    static public function pathImg(): string
    {
        return self::pathModule() . 'images/';
    }

    /**
     * 获取上传文件根路径
     */
    static private function pathUpload(): string
    {
        return self::pathRoot() . configDefault('/upload/', 'upload', 'path');
    }

    /**
     * 判断浏览器是否为IE
     */
    static private function isIE(): bool
    {
        if (!isset($_SERVER['HTTP_USER_AGENT'])) {
            return false;
        }
        return strpos($_SERVER["HTTP_USER_AGENT"], "MSIE") > 0;
    }

    /**
     * 确定一个JS文件的路径,包含时可以以开头/指定是全局脚本,否则尝试使用本模块的脚本
     *
     * @param string $file 文件名
     * @return string 路径
     */
    static private function dirCss(string $file): string
    {
        // 是否强制指定了全局
        if (substr($file, 0, 1) == '/') {
            return '/static/css' . $file . '.css';
        }

        // 当前模块
        if (self::$module) {
            // 尝试使用当前模块的
            $path = self::pathWithModule('/static', "css/{$file}.css");
            if (self::checkFile(self::$root . '/public' . $path)) {
                return $path;
            }
        }

        // 使用全局
        return '/static/css/' . $file . '.css';
    }

    /**
     * 拼接多个文件名,用于构造合并后的缓存文件名
     *
     * @param array $names 文件名数组
     * @return string 合并后的名字
     */
    static private function cacheBind(array $names): string
    {
        return md5(json($names));
    }

    private static function appendVer(string $path): string
    {
        return $path . '?v=' . configDefault('0', 'system', 'version');
    }

    /**
     * 在模板中包含样式文件
     * 可变参数: 样式文件名称列表
     * @param $files string[]
     * @return string
     */
    static public function css(string ... $files): string
    {
        // 如果没有参数,则忽略
        if (count($files) < 1) {
            return '';
        }

        // 如果只有一个参数,则直接包含
        if (count($files) == 1) {
            // 构造CSS文件的URL
            $url = self::pathRoot() . self::dirCss($files[0]);

            // 附加一个版本号
            $url = self::appendVer($url);

            // 返回HTML包含CSS的语法
            return "<link rel='stylesheet' type='text/css' href='$url' media='all'/>\n";
        }

        //构造缓存文件名
        $name = self::cacheBind($files);

        //缓存文件相对路径,相对public,中间以模块分隔
        $cachePath = self::pathWithModule('/static', "cache/{$name}.css");

        //缓存文件物理目录
        $cacheDir = self::$root . '/public' . $cachePath;

        // 如果合并文件不存在,则生成
        if (isDebug() or !self::checkFile($cacheDir)) {
            // 脚本内容
            $content = '';

            // 逐个脚本内容取出
            foreach (func_get_args() as $arg) {
                $fullName = self::$root . 'public' . self::dirCss($arg);

                $content .= "\r\n\r\n\r\n";
                // 内容合并
                $content .= file_get_contents($fullName) . "\r\n";
            }

            // 创建输出路径
            makeDir($cacheDir);

            // 写入缓存文件
            write($cacheDir, $content);
        }

        // 返回包含这合并文件的脚本语句
        $url = self::appendVer(self::pathRoot() . $cachePath);
        return "<link rel='stylesheet' type='text/css' href='$url'  media='all'/>\n";
    }

    /**
     * 拼接指定 模块的路径
     *
     * @param string $prefix 前缀
     * @param string $suffix 后缀
     * @return string 组合后的路径
     */
    static private function pathWithModule(string $prefix, string $suffix): string
    {
        $module = self::$module;

        if ($module) {
            return rtrim($prefix, '/') . '/' . $module . '/' . ltrim($suffix, '/');
        }
        return rtrim($prefix, '/') . '/' . ltrim($suffix, '/');
    }

    /**
     * 确定一个JS文件的相对Public目录的相对路径,包含时可以以开头/指定是全局脚本,否则尝试使用本模块的脚本
     *
     * @param string $file 文件名
     * @return string 路径
     */
    static private function dirJs(string $file): string
    {
        // 是否强制指定了全局脚本
        if (substr($file, 0, 1) == '/') {
            return '/static/js' . $file . '.js';
        }

        // 当前模块
        if (self::$module) {
            // 尝试使用当前模块的脚本
            $path = self::pathWithModule('/static', "js/{$file}.js");
            if (self::checkFile(self::$root . '/public' . $path)) {
                return $path;
            }
        }

        // 使用全局脚本
        return '/static/js/' . $file . '.js';
    }

    /**
     * 在模板中包含脚本文件
     * 可变参数: 脚本文件名称列表
     * @param $files string[]
     * @return string
     */
    static public function js(string ...$files): string
    {
        // 如果没有参数,则忽略
        if (count($files) < 1) {
            return '';
        }

        // 只有一个参数,则包含原来的文件
        if (count($files) == 1) {
            // 构造JS文件的URL
            $url = self::pathRoot() . self::dirJs($files[0]);

            // 调试模式,加上版本号
            $url = self::appendVer($url);

            // 构造 HTML中的JS包含语法
            return "\n";
        }

        // 文件名拼接
        $cacheName = self::cacheBind($files);
        $cachePath = self::pathWithModule('/static', "cache/{$cacheName}.js");
        $cacheDir = self::$root . '/public' . $cachePath;

        // 如果合并文件不存在,则生成
        if (isDebug() or !self::checkFile($cacheDir)) {
            // 脚本内容
            $content = '';

            // 逐个脚本内容取出
            foreach (func_get_args() as $arg) {
                $dirJs = self::dirJs($arg);

                //脚本文件的全路径
                $fullName = self::$root . 'public' . $dirJs;

                //加一个注释
                $content .= "\r\n\r\n{$dirJs}\r\n";

                // 内容合并
                $content .= file_get_contents($fullName) . "\n";
            }

            // 创建输出路径
            makeDir(dirname($cacheDir));

            // 写入缓存文件
            write($cacheDir, $content);
        }

        $url = self::appendVer(self::pathRoot() . $cachePath);
        return "\n";
    }

    /**
     * 简单模板替换
     * @param $template string 模板源
     * @param array $params 替换变量值 对
     * @return string 替换后的模板
     */
    static public function replace(string $template, array $params = []): string
    {
        foreach ($params as $k => $v) {
            $template = str_replace('{$' . $k . '}', $v, $template);
        }
        return $template;
    }

    /**
     * 取脚本文件路径
     */
    static public function pathJs(): string
    {
        return self::pathModule() . 'js/';
    }

    /**
     * 取所有静态资源所在路径
     */
    static public function pathStatic(): string
    {
        return self::pathRoot() . '/static/';
    }

    /**
     * 取样式表文件路径
     */
    static public function pathCss(): string
    {
        return self::pathModule() . 'css/';
    }

    /**
     * 判断文件是否存在
     *
     * @param $filename string 文件名及路径
     * @return bool
     */
    private static function checkFile(string $filename): bool
    {
        return file_exists($filename) and is_file($filename) and basename($filename) === basename(realpath($filename));
    }

}


<?php

namespace Potherca\Katwizy\Immutable;

class Environment
{
    ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
    const DEBUG = 'debug';
    const DEVELOPMENT = 'dev';
    const PRODUCTION = 'prod';

    private $environment;

    //////////////////////////// SETTERS AND GETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\
    private function getEnvironment()
    {
        if ($this->environment === null) {
            $this->environment = $this->createEnvironment();
        }

        return $this->environment;
    }

    //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    final public function __toString()
    {
        return $this->getEnvironment();
    }

    ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    private function createEnvironment()
    {
        $environment = Environment::PRODUCTION;

        $symfonyEnvironment = getenv('SYMFONY_ENV');

        if ($symfonyEnvironment !== false) {
            $environment = $symfonyEnvironment;
        }

        return $environment;
    }
}

/*EOF*/


<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Filter
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Abstract.php 24593 2012-01-05 20:35:02Z matthew $
 */

/**
 * @see Zend_Filter_PregReplace
 */

/**
 * @category   Zend
 * @package    Zend_Filter
 * @uses       Zend_Filter_PregReplace
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
abstract class Zend_Filter_Word_Separator_Abstract extends Zend_Filter_PregReplace
{

    protected $_separator = null;

    /**
     * Constructor
     *
     * @param  string $separator Space by default
     * @return void
     */
    public function __construct($separator = ' ')
    {
        $this->setSeparator($separator);
    }

    /**
     * Sets a new seperator
     *
     * @param  string  $separator  Seperator
     * @return $this
     */
    public function setSeparator($separator)
    {
        if ($separator == null) {

            throw new Zend_Filter_Exception('"' . $separator . '" is not a valid separator.');
        }
        $this->_separator = $separator;
        return $this;
    }

    /**
     * Returns the actual set seperator
     *
     * @return  string
     */
    public function getSeparator()
    {
        return $this->_separator;
    }

}


<?php

/*
 * Helper functions for building a DataTables server-side processing SQL query
 *
 * The static functions in this class are just helper functions to help build
 * the SQL used in the DataTables demo server-side processing scripts. These
 * functions obviously do not represent all that can be done with server-side
 * processing, they are intentionally simple to show how it works. More complex
 * server-side processing operations will likely require a custom script.
 *
 * See http://datatables.net/usage/server-side for full details on the server-
 * side processing requirements of DataTables.
 *
 * @license MIT - http://datatables.net/license_mit
 */

// REMOVE THIS BLOCK - used for DataTables test environment only!
$file = $_SERVER['DOCUMENT_ROOT'].'/datatables/mysql.php';
if ( is_file( $file ) ) {
	include( $file );
}

class SSP {
	/**
	 * Create the data output array for the DataTables rows
	 *
	 *  @param  array $columns Column information array
	 *  @param  array $data    Data from the SQL get
	 *  @return array          Formatted data in a row based format
	 */
	static function data_output ( $columns, $data )
	{
		$out = array();

		for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
			$row = array();

			for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
				$column = $columns[$j];

				// Is there a formatter?
				if ( isset( $column['formatter'] ) ) {
					$row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
				}
				else {
					$row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
				}
			}

			$out[] = $row;
		}

		return $out;
	}

	/**
	 * Paging
	 *
	 * Construct the LIMIT clause for server-side processing SQL query
	 *
	 *  @param  array $request Data sent to server by DataTables
	 *  @param  array $columns Column information array
	 *  @return string SQL limit clause
	 */
	static function limit ( $request, $columns )
	{
		$limit = '';

		if ( isset($request['start']) && $request['length'] != -1 ) {
			$limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
		}

		return $limit;
	}

	/**
	 * Ordering
	 *
	 * Construct the ORDER BY clause for server-side processing SQL query
	 *
	 *  @param  array $request Data sent to server by DataTables
	 *  @param  array $columns Column information array
	 *  @return string SQL order by clause
	 */
	static function order ( $request, $columns )
	{
		$order = '';

		if ( isset($request['order']) && count($request['order']) ) {
			$orderBy = array();
			$dtColumns = SSP::pluck( $columns, 'dt' );

			for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
				// Convert the column index into the column data property
				$columnIdx = intval($request['order'][$i]['column']);
				$requestColumn = $request['columns'][$columnIdx];

				$columnIdx = array_search( $requestColumn['data'], $dtColumns );
				$column = $columns[ $columnIdx ];

				if ( $requestColumn['orderable'] == 'true' ) {
					$dir = $request['order'][$i]['dir'] === 'asc' ?
						'ASC' :
						'DESC';

					$orderBy[] = '`'.$column['db'].'` '.$dir;
				}
			}

			$order = 'ORDER BY '.implode(', ', $orderBy);
		}

		return $order;
	}

	/**
	 * Searching / Filtering
	 *
	 * Construct the WHERE clause for server-side processing SQL query.
	 *
	 * NOTE this does not match the built-in DataTables filtering which does it
	 * word by word on any field. It's possible to do here performance on large
	 * databases would be very poor
	 *
	 *  @param  array $request Data sent to server by DataTables
	 *  @param  array $columns Column information array
	 *  @param  array $bindings Array of values for PDO bindings, used in the
	 *    sql_exec() function
	 *  @return string SQL where clause
	 */
	static function filter ( $request, $columns, &$bindings )
	{
		$globalSearch = array();
		$columnSearch = array();
		$dtColumns = SSP::pluck( $columns, 'dt' );

		if ( isset($request['search']) && $request['search']['value'] != '' ) {
			$str = $request['search']['value'];

			for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
				$requestColumn = $request['columns'][$i];
				$columnIdx = array_search( $requestColumn['data'], $dtColumns );
				$column = $columns[ $columnIdx ];

				if ( $requestColumn['searchable'] == 'true' ) {
					$binding = SSP::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
					$globalSearch[] = "`".$column['db']."` LIKE ".$binding;
				}
			}
		}

		// Individual column filtering
		for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
			$requestColumn = $request['columns'][$i];
			$columnIdx = array_search( $requestColumn['data'], $dtColumns );
			$column = $columns[ $columnIdx ];

			$str = $requestColumn['search']['value'];

			if ( $requestColumn['searchable'] == 'true' &&
			 $str != '' ) {
				$binding = SSP::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
				$columnSearch[] = "`".$column['db']."` LIKE ".$binding;
			}
		}

		// Combine the filters into a single string
		$where = '';

		if ( count( $globalSearch ) ) {
			$where = '('.implode(' OR ', $globalSearch).')';
		}

		if ( count( $columnSearch ) ) {
			$where = $where === '' ?
				implode(' AND ', $columnSearch) :
				$where .' AND '. implode(' AND ', $columnSearch);
		}

		if ( $where !== '' ) {
			$where = 'WHERE '.$where;
		}

		return $where;
	}

	/**
	 * Perform the SQL queries needed for an server-side processing requested,
	 * utilising the helper functions of this class, limit(), order() and
	 * filter() among others. The returned array is ready to be encoded as JSON
	 * in response to an SSP request, or can be modified if needed before
	 * sending back to the client.
	 *
	 *  @param  array $request Data sent to server by DataTables
	 *  @param  array $sql_details SQL connection details - see sql_connect()
	 *  @param  string $table SQL table to query
	 *  @param  string $primaryKey Primary key of the table
	 *  @param  array $columns Column information array
	 *  @return array          Server-side processing response array
	 */
	static function simple ( $request, $sql_details, $table, $primaryKey, $columns )
	{
		$bindings = array();
		$db = SSP::sql_connect( $sql_details );

		// Build the SQL query string from the request
		$limit = SSP::limit( $request, $columns );
		$order = SSP::order( $request, $columns );
		$where = SSP::filter( $request, $columns, $bindings );

		// Main query to actually get the data
		$data = SSP::sql_exec( $db, $bindings,
			"SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", SSP::pluck($columns, 'db'))."`
			 FROM `$table`
			 $where
			 $order
			 $limit"
		);

		// Data set length after filtering
		$resFilterLength = SSP::sql_exec( $db,
			"SELECT FOUND_ROWS()"
		);
		$recordsFiltered = $resFilterLength[0][0];

		// Total data set length
		$resTotalLength = SSP::sql_exec( $db,
			"SELECT COUNT(`{$primaryKey}`)
			 FROM   `$table`"
		);
		$recordsTotal = $resTotalLength[0][0];

		/*
		 * Output
		 */
		return array(
			"draw"            => intval( $request['draw'] ),
			"recordsTotal"    => intval( $recordsTotal ),
			"recordsFiltered" => intval( $recordsFiltered ),
			"data"            => SSP::data_output( $columns, $data )
		);
	}

	/**
	 * Connect to the database
	 *
	 * @param  array $sql_details SQL server connection details array, with the
	 *   properties:
	 *     * host - host name
	 *     * db   - database name
	 *     * user - user name
	 *     * pass - user password
	 * @return resource Database connection handle
	 */
	static function sql_connect ( $sql_details )
	{
		try {
			$db = @new PDO(
				"mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
				$sql_details['user'],
				$sql_details['pass'],
				array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
			);
		}
		catch (PDOException $e) {
			SSP::fatal(
				"An error occurred while connecting to the database. ".
				"The error reported by the server was: ".$e->getMessage()
			);
		}

		return $db;
	}

	/**
	 * Execute an SQL query on the database
	 *
	 * @param  resource $db  Database handler
	 * @param  array    $bindings Array of PDO binding values from bind() to be
	 *   used for safely escaping strings. Note that this can be given as the
	 *   SQL query string if no bindings are required.
	 * @param  string   $sql SQL query to execute.
	 * @return array         Result from the query (all rows)
	 */
	static function sql_exec ( $db, $bindings, $sql=null )
	{
		// Argument shifting
		if ( $sql === null ) {
			$sql = $bindings;
		}

		$stmt = $db->prepare( $sql );
		//echo $sql;

		// Bind parameters
		if ( is_array( $bindings ) ) {
			for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
				$binding = $bindings[$i];
				$stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
			}
		}

		// Execute
		try {
			$stmt->execute();
		}
		catch (PDOException $e) {
			SSP::fatal( "An SQL error occurred: ".$e->getMessage() );
		}

		// Return all
		return $stmt->fetchAll();
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Internal methods
	 */

	/**
	 * Throw a fatal error.
	 *
	 * This writes out an error message in a JSON string which DataTables will
	 * see and show to the user in the browser.
	 *
	 * @param  string $msg Message to send to the client
	 */
	static function fatal ( $msg )
	{
		echo json_encode( array(
			"error" => $msg
		) );

		exit(0);
	}

	/**
	 * Create a PDO binding key which can be used for escaping variables safely
	 * when executing a query with sql_exec()
	 *
	 * @param  array &$a    Array of bindings
	 * @param  *      $val  Value to bind
	 * @param  int    $type PDO field type
	 * @return string       Bound key to be used in the SQL where this parameter
	 *   would be used.
	 */
	static function bind ( &$a, $val, $type )
	{
		$key = ':binding_'.count( $a );

		$a[] = array(
			'key' => $key,
			'val' => $val,
			'type' => $type
		);

		return $key;
	}

	/**
	 * Pull a particular property from each assoc. array in a numeric array,
	 * returning and array of the property values from each item.
	 *
	 *  @param  array  $a    Array to get data from
	 *  @param  string $prop Property to read
	 *  @return array        Array of property values
	 */
	static function pluck ( $a, $prop )
	{
		$out = array();

		for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
			$out[] = $a[$i][$prop];
		}

		return $out;
	}
}


<?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\Events;

use Valkyrja\Application;
use Valkyrja\Events\Annotations\ListenerAnnotations;

/**
 * Class Events.
 *
 * @author Melech Mizrachi
 */
class NativeEvents implements Events
{
    /**
     * The application.
     *
     * @var \Valkyrja\Application
     */
    protected $app;

    /**
     * The event listeners.
     *
     * @var array
     */
    protected static $events = [];

    /**
     * Whether the container has been setup.
     *
     * @var bool
     */
    protected static $setup = false;

    /**
     * Events constructor.
     *
     * @param Application $application The application
     */
    public function __construct(Application $application)
    {
        $this->app = $application;
    }

    /**
     * Add an event listener.
     *
     * @param string   $event    The event
     * @param Listener $listener The event listener
     *
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function listen(string $event, Listener $listener): void
    {
        $this->add($event);

        $this->app->dispatcher()->verifyDispatch($listener);

        // If this listener has an id
        if (null !== $listener->getId()) {
            // Use it when setting to allow removal
            // or checking if it exists later
            self::$events[$event][$listener->getId()] = $listener;
        } else {
            // Otherwise set the listener normally
            self::$events[$event][] = $listener;
        }
    }

    /**
     * Add a listener to many events.
     *
     * @param Listener $listener  The listener
     * @param string[] ...$events The events
     *
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function listenMany(Listener $listener, string ...$events): void
    {
        // Iterate through the events
        foreach ($events as $event) {
            // Set a new listener for the event
            $this->listen($event, $listener);
        }
    }

    /**
     * Determine whether an event has a specified listener.
     *
     * @param string $event      The event
     * @param string $listenerId The event listener
     *
     * @return bool
     */
    public function hasListener(string $event, string $listenerId): bool
    {
        return $this->has($event) && isset(self::$events[$event][$listenerId]);
    }

    /**
     * Remove an event listener.
     *
     * @param string $event      The event
     * @param string $listenerId The event listener
     *
     * @return void
     */
    public function removeListener(string $event, string $listenerId): void
    {
        // If the listener exists
        if ($this->hasListener($event, $listenerId)) {
            // Unset it
            unset(self::$events[$event][$listenerId]);
        }
    }

    /**
     * Get the event's listeners.
     *
     * @param string $event The event
     *
     * @return \Valkyrja\Events\Listener[]
     */
    public function getListeners(string $event): array
    {
        return $this->has($event)
            ? self::$events[$event]
            : [];
    }

    /**
     * Determine whether an event has listeners.
     *
     * @param string $event The event
     *
     * @return bool
     */
    public function hasListeners(string $event): bool
    {
        return $this->has($event) && ! empty(self::$events[$event]);
    }

    /**
     * Add a new event.
     *
     * @param string $event The event
     *
     * @return void
     */
    public function add(string $event): void
    {
        if (! $this->has($event)) {
            self::$events[$event] = [];
        }
    }

    /**
     * Determine whether an event exists.
     *
     * @param string $event The event
     *
     * @return bool
     */
    public function has(string $event): bool
    {
        return isset(self::$events[$event]);
    }

    /**
     * Remove an event.
     *
     * @param string $event The event
     *
     * @return void
     */
    public function remove(string $event): void
    {
        if ($this->has($event)) {
            unset(self::$events[$event]);
        }
    }

    /**
     * Trigger an event.
     *
     * @param string $event     The event
     * @param array  $arguments [optional] The arguments
     *
     * @return array
     */
    public function trigger(string $event, array $arguments = null): array
    {
        // The responses
        $responses = [];

        if (! $this->has($event) || ! $this->hasListeners($event)) {
            return $responses;
        }

        // Iterate through all the event's listeners
        foreach ($this->getListeners($event) as $listener) {
            // Attempt to dispatch the event listener using any one of the
            // callable options
            $dispatch = $this->app->dispatcher()->dispatchCallable(
                $listener,
                $arguments
            );

            if (null !== $dispatch) {
                $responses[] = $dispatch;
            }
        }

        return $responses;
    }

    /**
     * Trigger an event interface.
     *
     * @param Event $event The event
     *
     * @return array
     */
    public function event(Event $event): array
    {
        return $this->trigger(\get_class($event), [$event]);
    }

    /**
     * Get all events.
     *
     * @return array
     */
    public function all(): array
    {
        return self::$events;
    }

    /**
     * Set the events.
     *
     * @param array $events The events
     *
     * @return void
     */
    public function set(array $events): void
    {
        self::$events = $events;
    }

    /**
     * Setup the events.
     *
     * @param bool $force    [optional] Whether to force setup
     * @param bool $useCache [optional] Whether to use cache
     *
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    public function setup(bool $force = false, bool $useCache = true): void
    {
        if (self::$setup && ! $force) {
            return;
        }

        self::$setup = true;

        // If the application should use the events cache files
        if ($useCache && $this->app->config()['events']['useCache']) {
            $this->setupFromCache();

            // Then return out of setup
            return;
        }

        self::$events = [];

        // If annotations are enabled and the events should use annotations
        if (
            $this->app->config()['events']['useAnnotations']
            && $this->app->config()['annotations']['enabled']
        ) {
            // Setup annotated event listeners
            $this->setupAnnotations();

            // If only annotations should be used
            if ($this->app->config()['events']['useAnnotationsExclusively']) {
                // Return to avoid loading events file
                return;
            }
        }

        // Include the events file
        require $this->app->config()['events']['filePath'];
    }

    /**
     * Setup the events from cache.
     *
     * @return void
     */
    protected function setupFromCache(): void
    {
        // Set the application events with said file
        $cache = $this->app->config()['cache']['events']
            ?? require $this->app->config()['events']['cacheFilePath'];

        self::$events = unserialize(
            base64_decode($cache['events'], true),
            [
                'allowed_classes' => [
                    Listener::class,
                ],
            ]
        );
    }

    /**
     * Setup annotations.
     *
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return void
     */
    protected function setupAnnotations(): void
    {
        /** @var ListenerAnnotations $containerAnnotations */
        $containerAnnotations = $this->app->container()->getSingleton(
            ListenerAnnotations::class
        );

        // Get all the annotated listeners from the list of classes
        $listeners = $containerAnnotations->getListeners(
            ...$this->app->config()['events']['classes']
        );

        // Iterate through the listeners
        foreach ($listeners as $listener) {
            // Set the service
            $this->listen($listener->getEvent(), $listener);
        }
    }

    /**
     * Get a cacheable representation of the events.
     *
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidClosureException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidDispatchCapabilityException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidFunctionException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidMethodException
     * @throws \Valkyrja\Dispatcher\Exceptions\InvalidPropertyException
     *
     * @return array
     */
    public function getCacheable(): array
    {
        $this->setup(true, false);

        return [
            'events' => base64_encode(serialize(self::$events)),
        ];
    }
}