<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Provider\Wechat;

use GuzzleHttp\Client;
use Mremi\UrlShortener\Exception\InvalidApiResponseException;
use Mremi\UrlShortener\Model\LinkInterface;
use Mremi\UrlShortener\Provider\Bitly\AuthenticationInterface;
use Mremi\UrlShortener\Provider\UrlShortenerProviderInterface;

/**
 * Wechat provider class.
 *
 * @author zacksleo <zacksleo@gmail.com>
 */
class WechatProvider implements UrlShortenerProviderInterface
{
    /**
     * @var AuthenticationInterface
     */
    private $auth;

    /**
     * @var array
     */
    private $options;

    /**
     * Constructor.
     *
     * @param AuthenticationInterface $auth    An authentication instance
     * @param array                   $options An array of options used to do the shorten/expand request
     */
    public function __construct(AuthenticationInterface $auth, array $options = array())
    {
        $this->auth    = $auth;
        $this->options = $options;
    }

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

    /**
     * {@inheritdoc}
     *
     * @param LinkInterface $link A link instance
     *
     * @throws InvalidApiResponseException
     */
    public function shorten(LinkInterface $link)
    {
        $client = $this->createClient();

        $response = $client->post(sprintf('/cgi-bin/shorturl?access_token=%s',
            $this->auth->getAccessToken()
        ), array_merge(
            array(
                'json' => array(
                    'action'   => 'long2short',
                    'long_url' => $link->getLongUrl(),
                ),
            ),
            $this->options
        ));

        $response = $this->validate($response->getBody()->getContents());

        $link->setShortUrl($response->short_url);
    }

    /**
     * {@inheritdoc}
     *
     * @param LinkInterface $link A link instance
     * @param string        $hash A Wechat hash
     *
     * @throws InvalidApiResponseException
     */
    public function expand(LinkInterface $link, $hash = null)
    {
        throw new InvalidApiResponseException('Wechat does not support expand url yet.');
    }

    /**
     * Creates a client.
     *
     * This method is mocked in unit tests in order to not make a real request,
     * so visibility must be protected or public.
     *
     * @return Client
     */
    protected function createClient()
    {
        return new Client(array(
            'base_uri' => 'https://api.weixin.qq.com',
        ));
    }

    /**
     * Validates the Wechat's response and returns it whether the status code is 200.
     *
     * @param string $apiRawResponse
     *
     * @return object
     *
     * @throws InvalidApiResponseException
     */
    private function validate($apiRawResponse)
    {
        $response = json_decode($apiRawResponse);

        if (null === $response) {
            throw new InvalidApiResponseException('Wechat response is probably mal-formed because cannot be json-decoded.');
        }

        if (!property_exists($response, 'errcode')) {
            throw new InvalidApiResponseException('Property "errcode" does not exist within Wechat response.');
        }

        if (0 !== $response->errcode) {
            throw new InvalidApiResponseException(sprintf('Wechat returned status code "%s" with message "%s"',
                $response->errcode,
                property_exists($response, 'errmsg') ? $response->errmsg : ''
            ));
        }

        return $response;
    }
}


<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Command;

use Mremi\UrlShortener\Model\Link;
use Mremi\UrlShortener\Provider\Baidu\BaiduProvider;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Shortens the long given URL using the Google API.
 *
 * @author zacksleo <zacksleo@gmail.com>
 */
class BaiduShortenCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('baidu:shorten')
            ->setDescription('Shortens the long given URL using the dwz.cn API')
            ->addArgument('long-url', InputArgument::REQUIRED, 'The long URL to shorten')
            ->addOption('options', null, InputOption::VALUE_REQUIRED, 'An array of options used by request');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $link = new Link();
        $link->setLongUrl($input->getArgument('long-url'));
        $options = $input->getOption('options') ? json_decode($input->getOption('options'), true) : array();

        $provider = new BaiduProvider($options);

        try {
            $provider->shorten($link);

            $output->writeln(sprintf('<info>Success:</info> %s', $link->getShortUrl()));
        } catch (\Exception $e) {
            $output->writeln(sprintf('<error>Failure:</error> %s', $e->getMessage()));
        }
    }
}


<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Provider\Bitly;

use GuzzleHttp\Client;
use Mremi\UrlShortener\Exception\InvalidApiResponseException;
use Mremi\UrlShortener\Model\LinkInterface;
use Mremi\UrlShortener\Provider\UrlShortenerProviderInterface;

/**
 * Bit.ly provider class.
 *
 * @author Rémi Marseille <marseille.remi@gmail.com>
 */
class BitlyProvider implements UrlShortenerProviderInterface
{
    /**
     * @var AuthenticationInterface
     */
    private $auth;

    /**
     * @var array
     */
    private $options;

    /**
     * Constructor.
     *
     * @param AuthenticationInterface $auth    An authentication instance
     * @param array                   $options An array of options used to do the shorten/expand request
     */
    public function __construct(AuthenticationInterface $auth, array $options = array())
    {
        $this->auth    = $auth;
        $this->options = $options;
    }

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

    /**
     * {@inheritdoc}
     *
     * @param LinkInterface $link   A link instance
     * @param string        $domain The domain to use, optional (bit.ly | j.mp | bitly.com)
     *
     * @throws InvalidApiResponseException
     */
    public function shorten(LinkInterface $link, $domain = null)
    {
        $client = $this->createClient();

        $response = $client->get(sprintf('/v3/shorten?access_token=%s&longUrl=%s&domain=%s',
            $this->auth->getAccessToken(),
            urlencode($link->getLongUrl()),
            $domain
        ), $this->options);

        $response = $this->validate($response->getBody()->getContents());

        $link->setShortUrl($response->data->url);
    }

    /**
     * {@inheritdoc}
     *
     * @param LinkInterface $link A link instance
     * @param string        $hash A Bit.ly hash
     *
     * @throws InvalidApiResponseException
     */
    public function expand(LinkInterface $link, $hash = null)
    {
        $client = $this->createClient();

        $response = $client->get(sprintf('/v3/expand?access_token=%s&shortUrl=%s&hash=%s',
            $this->auth->getAccessToken(),
            urlencode($link->getShortUrl()),
            $hash
        ), $this->options);

        $response = $this->validate($response->getBody()->getContents());

        $link->setLongUrl($response->data->expand[0]->long_url);
    }

    /**
     * Creates a client.
     *
     * This method is mocked in unit tests in order to not make a real request,
     * so visibility must be protected or public.
     *
     * @return Client
     */
    protected function createClient()
    {
        return new Client(array(
            'base_uri' => 'https://api-ssl.bitly.com',
        ));
    }

    /**
     * Validates the Bit.ly's response and returns it whether the status code is 200.
     *
     * @param string $apiRawResponse
     *
     * @return object
     *
     * @throws InvalidApiResponseException
     */
    private function validate($apiRawResponse)
    {
        $response = json_decode($apiRawResponse);

        if (null === $response) {
            throw new InvalidApiResponseException('Bit.ly response is probably mal-formed because cannot be json-decoded.');
        }

        if (!property_exists($response, 'status_code')) {
            throw new InvalidApiResponseException('Property "status_code" does not exist within Bit.ly response.');
        }

        if (200 !== $response->status_code) {
            throw new InvalidApiResponseException(sprintf('Bit.ly returned status code "%s" with message "%s"',
                $response->status_code,
                property_exists($response, 'status_txt') ? $response->status_txt : ''
            ));
        }

        return $response;
    }
}


<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Model;

use Mremi\UrlShortener\Provider\ChainProvider;

/**
 * Link manager class.
 *
 * @author Rémi Marseille <marseille.remi@gmail.com>
 */
class LinkManager implements LinkManagerInterface
{
    /**
     * @var ChainProvider
     */
    protected $chainProvider;

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

    /**
     * Constructor.
     *
     * @param ChainProvider $chainProvider A chain provider instance
     * @param string        $class         The Link class namespace, optional
     */
    public function __construct(ChainProvider $chainProvider, $class = 'Mremi\UrlShortener\Model\Link')
    {
        $this->chainProvider = $chainProvider;
        $this->class         = $class;
    }

    /**
     * {@inheritdoc}
     */
    public function create()
    {
        return new $this->class();
    }

    /**
     * {@inheritdoc}
     */
    public function findOneByProviderAndShortUrl($providerName, $shortUrl)
    {
        $provider = $this->chainProvider->getProvider($providerName);

        $link = $this->create();
        $link->setProviderName($provider->getName());
        $link->setShortUrl($shortUrl);

        $provider->expand($link);

        return $link;
    }

    /**
     * {@inheritdoc}
     */
    public function findOneByProviderAndLongUrl($providerName, $longUrl)
    {
        $provider = $this->chainProvider->getProvider($providerName);

        $link = $this->create();
        $link->setProviderName($provider->getName());
        $link->setLongUrl($longUrl);

        $provider->shorten($link);

        return $link;
    }
}


<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Provider\Sina;

use GuzzleHttp\Client;
use Mremi\UrlShortener\Exception\InvalidApiResponseException;
use Mremi\UrlShortener\Model\LinkInterface;
use Mremi\UrlShortener\Provider\UrlShortenerProviderInterface;

/**
 * Sina provider class.
 *
 * @author zacksleo <zacksleo@gmail.com>
 */
class SinaProvider implements UrlShortenerProviderInterface
{
    /**
     * @var string
     */
    private $apiKey;

    /**
     * @var array
     */
    private $options;

    /**
     * Constructor.
     *
     * @param string $apiKey  A Sina API key, optional
     * @param array  $options An array of options used to do the shorten/expand request
     */
    public function __construct($apiKey = null, array $options = array())
    {
        $this->apiKey = $apiKey;
        $this->options = $options;
    }
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'sina';
    }

    /**
     * {@inheritdoc}
     */
    public function shorten(LinkInterface $link)
    {
        $client = $this->createClient();

        $response = $client->get('shorten.json', array_merge(
            array(
                'query' => array(
                    'source'   => $this->apiKey,
                    'url_long' => $link->getLongUrl(),
                ),
            ),
            $this->options
        ));

        $response = $this->validate($response->getBody()->getContents());

        $link->setShortUrl($response->urls[0]->url_short);
    }

    /**
     * {@inheritdoc}
     */
    public function expand(LinkInterface $link)
    {
        $client = $this->createClient();

        $response = $client->get('expand.json', array_merge(
            array(
                'query' => array(
                    'source'    => $this->apiKey,
                    'url_short' => $link->getShortUrl(),
                ),
            ),
            $this->options
        ));

        $response = $this->validate($response->getBody()->getContents());

        $link->setLongUrl($response->urls[0]->url_long);
    }

    /**
     * Creates a client.
     *
     * This method is mocked in unit tests in order to not make a real request,
     * so visibility must be protected or public.
     *
     * @return Client
     */
    protected function createClient()
    {
        return new Client(array(
            'base_uri' => 'https://api.weibo.com/2/short_url/',
        ));
    }

    /**
     * Validates the Sina's response and returns it whether the status code is 200.
     *
     * @param string $apiRawResponse An API response, as it returned
     *
     * @return object
     *
     * @throws InvalidApiResponseException
     */
    private function validate($apiRawResponse)
    {
        $response = json_decode($apiRawResponse);

        if (null === $response) {
            throw new InvalidApiResponseException('Sina response is probably mal-formed because cannot be json-decoded.');
        }

        if (property_exists($response, 'error')) {
            throw new InvalidApiResponseException(sprintf(
                'Sina returned status code "%s" with message "%s".',
                property_exists($response, 'error_code') ? $response->error_code : '',
                property_exists($response, 'error') ? $response->error : ''
            ));
        }

        if (property_exists($response, 'urls')) {
            if (empty($response->urls[0]->url_long)) {
                throw new InvalidApiResponseException('Property "longUrl" does not exist within Sina response.');
            }
        }

        return $response;
    }
}


<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Command;

use Mremi\UrlShortener\Model\Link;
use Mremi\UrlShortener\Provider\Bitly\BitlyProvider;
use Mremi\UrlShortener\Provider\Bitly\OAuthClient;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Expands the short given URL using the Bitly API.
 *
 * @author Rémi Marseille <marseille.remi@gmail.com>
 */
class BitlyExpandCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('bitly:expand')
            ->setDescription('Expands the short given URL using the Bitly API')

            ->addArgument('username',  InputArgument::REQUIRED, 'A valid Bitly username')
            ->addArgument('password',  InputArgument::REQUIRED, 'A valid Bitly password')
            ->addArgument('short-url', InputArgument::REQUIRED, 'The short URL to expand')

            ->addOption('options', null, InputOption::VALUE_REQUIRED, 'An array of options used by request');
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $link = new Link();
        $link->setShortUrl($input->getArgument('short-url'));

        $options = $input->getOption('options') ? json_decode($input->getOption('options'), true) : array();

        $provider = new BitlyProvider(
            new OAuthClient($input->getArgument('username'), $input->getArgument('password')),
            $options
        );

        try {
            $provider->expand($link);

            $output->writeln(sprintf('<info>Success:</info> %s', $link->getLongUrl()));
        } catch (\Exception $e) {
            $output->writeln(sprintf('<error>Failure:</error> %s', $e->getMessage()));
        }
    }
}


<?php

/*
 * This file is part of the Mremi\UrlShortener library.
 *
 * (c) Rémi Marseille <marseille.remi@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Mremi\UrlShortener\Provider;

/**
 * Chain provider class.
 *
 * @author Rémi Marseille <marseille.remi@gmail.com>
 */
class ChainProvider
{
    /**
     * @var array
     */
    private $providers = array();

    /**
     * Adds the given provider to the chain.
     *
     * @param UrlShortenerProviderInterface $provider A provider instance
     */
    public function addProvider(UrlShortenerProviderInterface $provider)
    {
        $this->providers[$provider->getName()] = $provider;
    }

    /**
     * Gets a provider by name.
     *
     * @param string $name A provider name
     *
     * @return UrlShortenerProviderInterface
     *
     * @throws \RuntimeException If the provider does not exist
     */
    public function getProvider($name)
    {
        if (!$this->hasProvider($name)) {
            throw new \RuntimeException(sprintf('Unable to retrieve the provider named: "%s"', $name));
        }

        return $this->providers[$name];
    }

    /**
     * Gets the chain providers.
     *
     * @return UrlShortenerProviderInterface[]
     */
    public function getProviders()
    {
        return $this->providers;
    }

    /**
     * Returns TRUE whether the given name identifies a configured provider.
     *
     * @param string $name A provider name
     *
     * @return bool
     */
    public function hasProvider($name)
    {
        return array_key_exists($name, $this->providers);
    }
}


<?php

namespace SymfonyBundles\JsonRequestBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use SymfonyBundles\JsonRequestBundle\EventListener\RequestTransformerListener;

class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder()
    {
        $builder = new TreeBuilder('sb_json_request');

        if (\method_exists($builder, 'getRootNode')) {
            $rootNode = $builder->getRootNode();
        } else {
            // BC layer for symfony/config 4.1 and older
            $rootNode = $builder->root('maker');
        }

        $rootNode
            ->children()
                ->arrayNode('listener')
                    ->addDefaultsIfNotSet()
                    ->children()
                        ->scalarNode('request_transformer')
                            ->defaultValue(RequestTransformerListener::class)
                        ->end()
                        ->scalarNode('priority')
                            ->defaultValue(100)
                        ->end()
                    ->end()
                ->end()
            ->end();

        return $builder;
    }
}


<?php

namespace SymfonyBundles\JsonRequestBundle\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

class RequestTransformerListener implements RequestListenerInterface
{
    /**
     * {@inheritdoc}
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if (false === $this->isAvailable($request)) {
            return;
        }

        if (false === $this->transform($request)) {
            $response = Response::create('Unable to parse request.', 400);

            $event->setResponse($response);
        }
    }

    /**
     * @param Request $request
     *
     * @return bool
     */
    private function isAvailable(Request $request)
    {
        return 'json' === $request->getContentType() && $request->getContent();
    }

    /**
     * @param Request $request
     *
     * @return bool
     */
    private function transform(Request $request)
    {
        $data = json_decode($request->getContent(), true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            return false;
        }

        if (is_array($data)) {
            $request->request->replace($data);
        }

        return true;
    }
}


<?php

namespace SymfonyBundles\JsonRequestBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;

class JsonRequestExtension extends ConfigurableExtension
{
    /**
     * {@inheritdoc}
     */
    protected function loadInternal(array $configs, ContainerBuilder $container)
    {
        $listener = new Definition($configs['listener']['request_transformer']);

        $listener->addTag('kernel.event_listener', [
            'event' => 'kernel.request',
            'method' => 'onKernelRequest',
            'priority' => $configs['listener']['priority'],
        ]);

        $container->setDefinition('sb_json_request.request_transformer', $listener);
    }

    /**
     * {@inheritdoc}
     */
    public function getAlias()
    {
        return 'sb_json_request';
    }
}


<?php

namespace Matthewbdaly\ETagMiddleware;

use Closure;
use Illuminate\Http\Request;

/**
 * ETag middleware.
 */
class ETag
{
    /**
     * Implement Etag support.
     *
     * @param \Illuminate\Http\Request $request The HTTP request.
     * @param \Closure                 $next    Closure for the response.
     *
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        // If this was not a get or head request, just return
        if (!$request->isMethod('get') && !$request->isMethod('head')) {
            return $next($request);
        }

        // Get the initial method sent by client
        $initialMethod = $request->method();

        // Force to get in order to receive content
        $request->setMethod('get');

        // Get response
        $response = $next($request);

        // Generate Etag
        $etag = md5(json_encode($response->headers->get('origin')).$response->getContent());

        // Load the Etag sent by client
        $requestEtag = str_replace('"', '', $request->getETags());

        // Check to see if Etag has changed
        if ($requestEtag && $requestEtag[0] == $etag) {
            $response->setNotModified();
        }

        // Set Etag
        $response->setEtag($etag);

        // Set back to original method
        $request->setMethod($initialMethod); // set back to original method

        // Send response
        return $response;
    }
}


<?php

namespace ipinfo\ipinfo;

require_once(__DIR__.'/cache/Default.php');

use Exception;
use GuzzleHttp\Exception\GuzzleException;
use ipinfo\ipinfo\Details;
use ipinfo\ipinfo\IPinfoException;

/**
 * Exposes the IPinfo library to client code.
 */
class IPinfo
{
    const API_URL = 'https://ipinfo.io';
    const CACHE_MAXSIZE = 4096;
    const CACHE_TTL = 86400; // 24 hours as seconds
    const COUNTRIES_FILE_DEFAULT = __DIR__ . '/countries.json';
    const REQUEST_TYPE_GET = 'GET';
    const STATUS_CODE_QUOTA_EXCEEDED = 429;

    public $access_token;
    public $cache;
    public $countries;
    protected $http_client;

    public function __construct($access_token = null, $settings = [])
    {
        $this->access_token = $access_token;
        $this->http_client = new \GuzzleHttp\Client(['http_errors' => false]);

        $countries_file = $settings['countries_file'] ?? self::COUNTRIES_FILE_DEFAULT;
        $this->countries = $this->readCountryNames($countries_file);

        if (array_key_exists('cache', $settings)) {
          $this->cache = $settings['cache'];
        } else {
          $maxsize = $settings['cache_maxsize'] ?? self::CACHE_MAXSIZE;
          $ttl = $settings['cache_ttl'] ?? self::CACHE_TTL;
          $this->cache = new cache\DefaultCache($maxsize, $ttl);
        }
    }

    /**
     * Get formatted details for an IP address.
     * @param  string|null $ip_address IP address to look up.
     * @return Details Formatted IPinfo data.
     * @throws IPinfoException
     */
    public function getDetails($ip_address = null)
    {
        $response_details = $this->getRequestDetails((string) $ip_address);

        return $this->formatDetailsObject($response_details);
    }

    /**
     * Format details and return as an object.
     * @param  array  $details IP address details.
     * @return Details Formatted IPinfo Details object.
     */
    public function formatDetailsObject($details = [])
    {
      $country = $details['country'] ?? null;
      $details['country_name'] = $this->countries[$country] ?? null;

      if (array_key_exists('loc', $details)) {
        $coords = explode(',', $details['loc']);
        $details['latitude'] = $coords[0];
        $details['longitude'] = $coords[1];
      } else {
        $details['latitude'] = null;
        $details['longitude'] = null;
      }

      return new Details($details);
    }

    /**
     * Get details for a specific IP address.
     * @param  string $ip_address IP address to query API for.
     * @return array IP response data.
     * @throws IPinfoException
     */
    public function getRequestDetails(string $ip_address)
    {
      if (!$this->cache->has($ip_address)) {
        $url = self::API_URL;
        if ($ip_address) {
          $url .= "/$ip_address";
        }

        try {
          $response = $this->http_client->request(
            self::REQUEST_TYPE_GET,
            $url,
            $this->buildHeaders()
          );
        } catch (GuzzleException $e) {
          throw new IPinfoException($e->getMessage());
        } catch (Exception $e) {
          throw new IPinfoException($e->getMessage());
        }

        if ($response->getStatusCode() == self::STATUS_CODE_QUOTA_EXCEEDED) {
          throw new IPinfoException('IPinfo request quota exceeded.');
        } elseif ($response->getStatusCode() >= 400) {
          throw new IPinfoException('Exception: ' . json_encode([
            'status' => $response->getStatusCode(),
            'reason' => $response->getReasonPhrase(),
          ]));
        }

        $raw_details = json_decode($response->getBody(), true);
        $this->cache->set($ip_address, $raw_details);
      }

      return $this->cache->get($ip_address);
    }

    /**
     * Build headers for API request.
     * @return array Headers for API request.
     */
    public function buildHeaders()
    {
      $headers = [
        'user-agent' => 'IPinfoClient/PHP/1.0',
        'accept' => 'application/json',
      ];

      if ($this->access_token) {
        $headers['authorization'] = "Bearer {$this->access_token}";
      }

      return ['headers' => $headers];
    }

    /**
     * Read country names from a file and return as an array.
     * @param  string $countries_file JSON file of country_code => country_name mappings
     * @return array country_code => country_name mappings
     */
    private function readCountryNames($countries_file)
    {
      $file_contents = file_get_contents($countries_file);
      return json_decode($file_contents, true);
    }
}


<?php

namespace ipinfo\ipinfo\cache;
use ipinfo\ipinfo\cache\CacheInterface;

require_once(__DIR__ . '/Interface.php');

/**
 * Default implementation of the CacheInterface. Provides in-memory caching.
 */
class DefaultCache implements CacheInterface {

  public $maxsize;
  public $ttl;

  private $cache;
  private $element_queue;

  public function __construct(int $maxsize, int $ttl)
  {
    $this->cache = new \Sabre\Cache\Memory();
    $this->element_queue;
    $this->maxsize = $maxsize;
    $this->ttl = $ttl;
  }

  /**
   * Tests if the specified IP address is cached.
   * @param  string  $ip_address IP address to lookup.
   * @return boolean Is the IP address data in the cache.
   */
  public function has(string $name)
  {
    return $this->cache->has($name);
  }

  /**
   * Set the IP address key to the specified value.
   * @param string $ip_address IP address to cache data for.
   * @param mixed $value Data for specified IP address.
   */
  public function set(string $name, $value)
  {
      if (!$this->cache->has($name)) {
        $this->element_queue[] = $name;
      }

      $this->cache->set($name, $value, $this->ttl);

      $this->manageSize();
  }

  /**
   * Get data for the specied IP address.
   * @param  string $ip_address IP address to lookup in cache.
   * @return mixed IP address data.
   */
  public function get(string $name)
  {
    return $this->cache->get($name);
  }

  /**
   * If cache maxsize has been reached, remove oldest elements until limit is reached.
   */
  private function manageSize()
  {
    $overflow = count($this->element_queue) - $this->maxsize;
    if ($overflow > 0) {
        foreach (array_slice($this->element_queue, 0, $overflow) as $name) {
          if ($this->cache->has($name)) {
            $this->cache->delete($name);
          }
        }
        $this->element_queue = array_slice($this->element_queue, $overflow);
    }
  }
}


<?php

abstract class Paymentwall_ApiObject extends Paymentwall_Instance
{
	const API_BRICK_SUBPATH			= 'brick';
	const API_OBJECT_CHARGE 		= 'charge';
	const API_OBJECT_SUBSCRIPTION 	= 'subscription';
	const API_OBJECT_ONE_TIME_TOKEN = 'token';

	protected $properties = array();
	protected $_id;
	protected $_rawResponse = '';
	protected $_responseLogInformation = array();
	protected $brickSubEndpoints = array(
		self::API_OBJECT_CHARGE, self::API_OBJECT_SUBSCRIPTION, self::API_OBJECT_ONE_TIME_TOKEN
	);

	abstract function getEndpointName();

	public function __construct($id = '')
	{
		if (!empty($id)) {
			$this->_id = $id;
		}
	}

	public final function create($params = array())
	{
		$httpAction = new Paymentwall_HttpAction($this, $params, array(
			$this->getApiBaseHeader()
		));
		$this->setPropertiesFromResponse($httpAction->run());
		return $this;
	}

	public function __get($property)
	{
		return isset($this->properties[$property]) ? $this->properties[$property] : null;
	}

	public function getApiUrl()
	{
		if ($this->getEndpointName() === self::API_OBJECT_ONE_TIME_TOKEN && !$this->getConfig()->isTest()) {
			return Paymentwall_OneTimeToken::GATEWAY_TOKENIZATION_URL;
		} else {
			return $this->getApiBaseUrl() . $this->getSubPath() . '/' . $this->getEndpointName();
		}
	}

	/**
	 * Returns raw data about the response that can be presented to the end-user:
	 * 	success => 0 or 1
	 *	error =>
	 *		message 	- human-readable error message
	 *		code 		- error code, see https://www.paymentwall.com/us/documentation/Brick/2968#error
	 * 	secure =>
	 *		formHTML 	- needed to complete 3D Secure step, HTML of the form to be submitted to the user to redirect him to the bank page
	 *
	 * @return array
	 *
	 */
	public function _getPublicData()
	{
		/*$responseModel = Paymentwall_Response_Factory::get($this->getPropertiesFromResponse());
		return $responseModel instanceof Paymentwall_Response_Interface ? $responseModel->process() : '';*/

		/**
		 * @todo encapsulate this into Paymentwall_Response_Factory better; right now it returns success=1 for 3ds case
		 */
		$response = $this->getPropertiesFromResponse();
		$result = array();
		if (isset($response['type']) && $response['type'] == 'Error') {
			$result = array(
				'success' => 0,
				'error' => array(
					'message' => $response['error'],
					'code' => $response['code']
				)
			);
		}
		elseif (!empty($response['secure'])) {
			$result = array(
				'success' => 0,
				'secure' => $response['secure']
			);
		}
		elseif ($this->isSuccessful()) {
			$result['success'] = 1;
		}
		else {
			$result = array(
				'success' => 0,
				'error' => array(
					'message' => 'Internal error'
				)
			);
		}
		return $result;
	}

	/**
	 * @return string json encoded result of ApiObject::getPublicData()
	 */
	public function getPublicData()
	{
		return json_encode($this->_getPublicData());
	}

	public function getProperties() {
		return $this->properties;
	}

	public function getRawResponseData()
	{
		return $this->_rawResponse;
	}

	protected function setPropertiesFromResponse($response = '')
	{
		if (!empty($response)) {
			$this->_rawResponse = $response;
			$this->properties = (array) $this->preparePropertiesFromResponse($response);
		} else {
			throw new Exception('Empty response');
		}
	}

	protected function getSubPath()
	{
		return (in_array($this->getEndpointName(), $this->brickSubEndpoints))
				? '/' . self::API_BRICK_SUBPATH
				: '';
	}

	protected function getPropertiesFromResponse()
	{
		return $this->properties;
	}

	protected function preparePropertiesFromResponse($string = '')
	{
		return json_decode($string, false);
	}

	protected function getApiBaseHeader()
	{
		return 'X-ApiKey: ' . $this->getPrivateKey();
	}

	protected function doApiAction($action = '', $method = 'post')
	{
		$actionUrl = $this->getApiUrl() . '/' . $this->_id . '/' . $action;
		$httpAction = new Paymentwall_HttpAction($this, array('id' => $this->_id), array(
			$this->getApiBaseHeader()
		));
		$this->_responseLogInformation = $httpAction->getResponseLogInformation();
		$this->setPropertiesFromResponse(
			$method == 'get' ? $httpAction->get($actionUrl) : $httpAction->post($actionUrl)
		);

		return $this;
	}

	public function getResponseLogInformation()
	{
		return $this->_responseLogInformation;
	}

}


<?php

class Paymentwall_GenerericApiObject extends Paymentwall_ApiObject
{
	/**
	 * API type
	 *
	 * @var string
	 */
	protected $api;

	/**
	 * Paymentwall_HttpAction object
	 *
	 * @var \Paymentwall_HttpAction
	 */
	protected $httpAction;

	/**
	 * @see \Paymentwall_ApiObject
	 */
	public function getEndpointName()
	{
		return $this->api;
	}

	public function __construct($type)
	{
		$this->api = $type;
		$this->httpAction = new Paymentwall_HttpAction($this);
	}

	/**
	 * Make post request
	 *
	 * @param array $params
	 * @param array $headers
	 *
	 * @return array
	 */
	public function post($params = array(), $headers = array())
	{
		if (empty($params)) {
			return null;
		}

		$this->httpAction->setApiParams($params);

		$this->httpAction->setApiHeaders(array_merge(array($this->getApiBaseHeader()), $headers));

		return (array) $this->preparePropertiesFromResponse(
			$this->httpAction->post(
				$this->getApiUrl()
			)
		);
	}
}


<?php

/**
 * This file is part of ethereum-tx package.
 *
 * (c) Kuan-Cheng,Lai <alk03073135@gmail.com>
 *
 * @author Peter Lai <alk03073135@gmail.com>
 * @license MIT
 */

namespace Web3p\EthereumTx;

use InvalidArgumentException;
use RuntimeException;
use Web3p\RLP\RLP;
use Elliptic\EC;
use Elliptic\EC\KeyPair;
use ArrayAccess;
use Web3p\EthereumUtil\Util;

class Transaction implements ArrayAccess
{
    /**
     * attributeMap
     *
     * @var array
     */
    protected $attributeMap = [
        'from' => [
            'key' => -1
        ],
        'chainId' => [
            'key' => -2
        ],
        'nonce' => [
            'key' => 0,
            'length' => 32,
            'allowLess' => true,
            'allowZero' => false
        ],
        'gasPrice' => [
            'key' => 1,
            'length' => 32,
            'allowLess' => true,
            'allowZero' => false
        ],
        'gasLimit' => [
            'key' => 2,
            'length' => 32,
            'allowLess' => true,
            'allowZero' => false
        ],
        'gas' => [
            'key' => 2,
            'length' => 32,
            'allowLess' => true,
            'allowZero' => false
        ],
        'to' => [
            'key' => 3,
            'length' => 20,
            'allowZero' => true,
        ],
        'value' => [
            'key' => 4,
            'length' => 32,
            'allowLess' => true,
            'allowZero' => false
        ],
        'data' => [
            'key' => 5,
            'allowLess' => true,
            'allowZero' => true
        ],
        'v' => [
            'key' => 6,
            'allowZero' => true
        ],
        'r' => [
            'key' => 7,
            'length' => 32,
            'allowZero' => true
        ],
        's' => [
            'key' => 8,
            'length' => 32,
            'allowZero' => true
        ]
    ];

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

    /**
     * rlp
     *
     * @var \Web3p\RLP\RLP
     */
    protected $rlp;

    /**
     * secp256k1
     *
     * @var \Elliptic\EC
     */
    protected $secp256k1;

    /**
     * privateKey
     *
     * @var \Elliptic\EC\KeyPair
     */
    protected $privateKey;

    /**
     * util
     *
     * @var \Web3p\EthereumUtil\Util
     */
    protected $util;

    /**
     * construct
     *
     * @param array|string $txData
     * @return void
     */
    public function __construct($txData=[])
    {
        $this->rlp = new RLP;
        $this->secp256k1 = new EC('secp256k1');
        $this->util = new Util;

        if (is_array($txData)) {
            foreach ($txData as $key => $data) {
                $this->offsetSet($key, $data);
            }
        } elseif (is_string($txData)) {
            $tx = [];

            if ($this->util->isHex($txData)) {
                $txData = $this->rlp->decode($txData);

                foreach ($txData as $txKey => $data) {
                    if (is_int($txKey)) {
                        $hexData = $data->toString('hex');

                        if (strlen($hexData) > 0) {
                            $tx[$txKey] = '0x' . $hexData;
                        } else {
                            $tx[$txKey] = $hexData;
                        }
                    }
                }
            }
            $this->txData = $tx;
        }
    }

    /**
     * get
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name)
    {
        $method = 'get' . ucfirst($name);

        if (method_exists($this, $method)) {
            return call_user_func_array([$this, $method], []);
        }
        return $this->offsetGet($name);
    }

    /**
     * set
     *
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public function __set($name, $value)
    {
        $method = 'set' . ucfirst($name);

        if (method_exists($this, $method)) {
            return call_user_func_array([$this, $method], [$value]);
        }
        return $this->offsetSet($name, $value);
    }

    /**
     * toString
     *
     * @return string
     */
    public function __toString()
    {
        return $this->hash(false);
    }

    /**
     * offsetSet
     *
     * @param string $offset
     * @param string $value
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null;

        if (is_array($txKey)) {
            $checkedValue = ($value) ? (string) $value : '';
            $isHex = $this->util->isHex($checkedValue);
            $checkedValue = $this->util->stripZero($checkedValue);

            if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) {
                // check length
                if (isset($txKey['length'])) {
                    if ($isHex) {
                        if (strlen($checkedValue) > $txKey['length'] * 2) {
                            throw new InvalidArgumentException($offset . ' exceeds the length limit.');
                        }
                    } else {
                        if (strlen($checkedValue) > $txKey['length']) {
                            throw new InvalidArgumentException($offset . ' exceeds the length limit.');
                        }
                    }
                }
            }
            if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) {
                // check zero
                if (preg_match('/^0*$/', $checkedValue) === 1) {
                    // set value to empty string
                    $value = '';
                }
            }
            $this->txData[$txKey['key']] = $value;
        }
    }

    /**
     * offsetExists
     *
     * @param string $offset
     * @return bool
     */
    public function offsetExists($offset)
    {
        $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null;

        if (is_array($txKey)) {
            return isset($this->txData[$txKey['key']]);
        }
        return false;
    }

    /**
     * offsetUnset
     *
     * @param string $offset
     * @return void
     */
    public function offsetUnset($offset)
    {
        $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null;

        if (is_array($txKey) && isset($this->txData[$txKey['key']])) {
            unset($this->txData[$txKey['key']]);
        }
    }

    /**
     * offsetGet
     *
     * @param string $offset
     * @return mixed
     */
    public function offsetGet($offset)
    {
        $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null;

        if (is_array($txKey) && isset($this->txData[$txKey['key']])) {
            return $this->txData[$txKey['key']];
        }
        return null;
    }

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

    /**
     * serialize
     *
     * @return \Web3p\RLP\RLP\Buffer
     */
    public function serialize()
    {
        $chainId = $this->offsetGet('chainId');

        // sort tx data
        if (ksort($this->txData) !== true) {
            throw new RuntimeException('Cannot sort tx data by keys.');
        }
        if ($chainId && $chainId > 0) {
            $txData = array_fill(0, 9, '');
        } else {
            $txData = array_fill(0, 6, '');
        }
        foreach ($this->txData as $key => $data) {
            if ($key >= 0) {
                $txData[$key] = $data;
            }
        }
        return $this->rlp->encode($txData);
    }

    /**
     * sign
     *
     * @param string $privateKey
     * @return string
     */
    public function sign(string $privateKey)
    {
        $txHash = $this->hash(false);
        $privateKey = $this->secp256k1->keyFromPrivate($privateKey, 'hex');
        $signature = $privateKey->sign($txHash, [
            'canonical' => true
        ]);
        $r = $signature->r;
        $s = $signature->s;
        $v = $signature->recoveryParam + 35;

        $chainId = $this->offsetGet('chainId');

        if ($chainId && $chainId > 0) {
            $v += (int) $chainId * 2;
        }

        $this->offsetSet('r', '0x' . $r->toString(16));
        $this->offsetSet('s', '0x' . $s->toString(16));
        $this->offsetSet('v', $v);
        $this->privateKey = $privateKey;

        return $this->serialize()->toString('hex');
    }

    /**
     * hash
     *
     * @param bool $includeSignature
     * @return string
     */
    public function hash($includeSignature=false)
    {
        $chainId = $this->offsetGet('chainId');

        // sort tx data
        if (ksort($this->txData) !== true) {
            throw new RuntimeException('Cannot sort tx data by keys.');
        }
        if ($includeSignature) {
            $txData = $this->txData;
        } else {
            $rawTxData = $this->txData;

            if ($chainId && $chainId > 0) {
                $v = (int) $chainId;
                $this->offsetSet('r', '');
                $this->offsetSet('s', '');
                $this->offsetSet('v', $v);
                $txData = array_fill(0, 9, '');
            } else {
                $txData = array_fill(0, 6, '');
            }

            foreach ($this->txData as $key => $data) {
                if ($key >= 0) {
                    $txData[$key] = $data;
                }
            }
            $this->txData = $rawTxData;
        }
        $serializedTx = $this->rlp->encode($txData)->toString('utf8');

        return $this->util->sha3($serializedTx);
    }

    /**
     * getFromAddress
     *
     * @return string
     */
    public function getFromAddress()
    {
        $from = $this->offsetGet('from');

        if ($from) {
            return $from;
        }
        if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) {
            // recover from hash
            $r = $this->offsetGet('r');
            $s = $this->offsetGet('s');
            $v = $this->offsetGet('v');
            $chainId = $this->offsetGet('chainId');

            if (!$r || !$s) {
                throw new RuntimeException('Invalid signature r and s.');
            }
            $txHash = $this->hash(false);

            if ($chainId && $chainId > 0) {
                $v -= ($chainId * 2);
            }
            $v -= 35;
            $publicKey = $this->secp256k1->recoverPubKey($txHash, [
                'r' => $r,
                's' => $s
            ], $v);
            $publicKey = $publicKey->encode('hex');
        } else {
            $publicKey = $this->privateKey->getPublic(false, 'hex');
        }
        $from = '0x' . substr($this->util->sha3(substr(hex2bin($publicKey), 1)), 24);

        $this->offsetSet('from', $from);
        return $from;
    }
}


<?php
namespace WorkerF;

/**
 * Error.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Error
{

    /**
     * response html.
     *
     * @var string
     */
    protected static $_html_blade = <<<EOF
<!DOCTYPE html>

    <title>{{title}}</title>

{{header}}

{{exception}}

EOF;
    /**
     * print error.
     *
     * @param  mixed $e
     * @return void
     */
    public static function printError($e)
    {
        echo '['.date('Y-m-d H:i:s', time()).']'."\n".$e."\n";
    }

    /**
     * return error html.
     *
     * @param  mixed $e
     * @param  int $header
     * @param  boolean $debug
     * @return string
     */
    public static function errorHtml($e, $header, $debug = TRUE)
    {
        $pattern = [
            '/\{\{title\}\}/',
            '/\{\{header\}\}/',
            '/\{\{exception\}\}/',
        ];

        $title = $header;
        $exception = $debug ? $e : 'something error...';
        $replacement = [$title, $header, $exception];

        return preg_replace($pattern, $replacement, self::$_html_blade);
    }
}


<?php
namespace WorkerF\Http;
use WorkerF\Http\Requests;
use WorkerF\Tool\Pipeline;
use WorkerF\IOCContainer;

/**
 * Middleware.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Middleware
{
    /**
     * dispatch route.
     *
     * @param array $middlewares
     * @param WorkerF\Http\Requests $request
     * @return mixed
     */
    public static function run(array $middlewares, Requests $request)
    {
        // if middlewares empty, return request as is
        if (empty($middlewares)) {
            return $request;
        }

        $pipes = [];

        foreach ($middlewares as $middleware) {
            // get instance with singleton
            $middleware_instance = IOCContainer::getInstanceWithSingleton($middleware);
            // create pipes array
            $pipes[] = [$middleware_instance, 'handle'];
        }

        $pipeline = new Pipeline($pipes);
        // run pipes flow
        return $pipeline->flow($request);
    }
}


<?php
namespace WorkerF\Http;
use WorkerF\Http\Requests;
use WorkerF\Http\Response;
use WorkerF\IOCContainer;
use WorkerF\Config;
use Closure;
/**
 * HTTP router.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Route {
    /**
     * The route map.
     *
     * @var array
     */
    protected static $_map_tree = [];

    /**
     * The route middleware map.
     *
     * @var array
     */
    protected static $_middleware_map_tree = [];

    /**
     * The variable route map.
     *
     * @var array
     */
    protected static $_variable_map_tree = [];

    /**
     * The variable route cache, avoid foreach regexp match everytime.
     * Use route cache, time complexity from O(n) to O(1)
     *
     * @var array
     */
    protected static $_variable_route_cache = [];

    /**
     * The variable route cache index, Used for cache control with LRU.
     *
     * @var array
     */
    protected static $_variable_route_cache_index = [];

    /**
     * The variable route cache limit, default 2^13.
     *
     * @var array
     */
    protected static $_variable_route_cache_limit = 8192;

    /**
     * The variable route regexp mode.
     * accept letter, underscore. cannot start with a number
     *
     * @var array
     */
    protected static $_variable_regexp = '/\{([a-z|A-Z|_]+[0-9]*)\}/';

    /**
     * The variable route regexp replacement.
     * accept letter, underscore, number
     *
     * @var array
     */
    protected static $_variable_replacement = '([0-9|a-z|A-Z|_]+)';

    /**
     * route config filter.
     *
     * @var array
     */
    protected static $_filter = [
        'prefix'     => '',
        'namespace'  => '',
        'middleware' => [],
    ];

    /**
     * call method (http methods).
     *
     * @param  string  $method
     * @param  mixed  $params string or Closure
     * @return void
     * @throws \InvalidArgumentException run out of worker container, not catch, just crash
     */
    public static function __callstatic($method, $params)
    {
        // $param check
        if (count($params) !== 2) {
            // not catch, trigger Fatal error
            throw new \InvalidArgumentException("method $method accept 2 params!");
        }
        // set map tree
        self::_setMapTree($method, $params[0], $params[1]);
    }

    /**
     * set group route.
     *
     * @param  array    $filter
     * @param  \Closure  $routes
     * @return void
     */
    public static function group(array $filter, Closure $routes)
    {
        // save sttribute
        $tmp_prefix    = self::$_filter['prefix'];
        $tmp_namespace = self::$_filter['namespace'];
        $middleware    = self::$_filter['middleware'];

        // set filter path prefix
        if (isset($filter['prefix'])) {
            self::$_filter['prefix'] .= '/'.$filter['prefix'].'/';
        }
        // set filter namespace prefix
        if (isset($filter['namespace'])) {
            self::$_filter['namespace'] .= '\\'.$filter['namespace'].'\\';
        }
        // set filter middleware
        if (isset($filter['middleware'])) {
            self::$_filter['middleware'][] = $filter['middleware'];
        }
        // call route setting
        call_user_func($routes);
        // recover sttribute
        self::$_filter['prefix']     = $tmp_prefix;
        self::$_filter['namespace']  = $tmp_namespace;
        self::$_filter['middleware'] = $middleware;
    }

    /**
     * Parse path.
     *
     * @param  string  $path
     * @return string
     */
    protected static function _pathParse($path)
    {
        // make path as /a/b/c mode
        $path = ($path == '/') ? $path : '/'.rtrim($path, '/');
        $path = preg_replace('/\/+/', '/', $path);

        return $path;
    }

    /**
     * Parse namespace.
     *
     * @param  string  $namespace
     * @return string
     */
    protected static function _namespaceParse($namespace)
    {
        // make namespace as \a\b\c mode
        // why 4 '\' ? see php document preg_replace
        return preg_replace('/\\\\+/', '\\\\', $namespace);
    }

    /**
     * check route is variable route or not.
     *
     * @param  string  $path
     * @return boolean
     */
    protected static function _isVariableRoute($path)
    {
        $matched = [];

        preg_match_all(self::$_variable_regexp, $path, $matched);

        if (empty($matched[0])) {
            return FALSE;
        }

        return TRUE;
    }

    /**
     * replace variable route path to regexp string.
     *
     * @param  string  $path
     * @return string
     */
    protected static function _variablePathReplace($path)
    {
        return '@^'.preg_replace(self::$_variable_regexp, self::$_variable_replacement, $path).'$@';
    }

    /**
     * Parse variable route info (like params, route callback).
     *
     * @param  string  $path
     * @param  string  $method
     * @return mixed string or NULL
     */
    protected static function _variablePathParse($path, $method)
    {
        // Why use array_reverse? Because if route is renamed, we want to use the last one
        $variable_paths = array_keys(array_reverse(self::$_variable_map_tree));

        $path_info = [];
        // variable route matched or not
        foreach ($variable_paths as $variable_path) {

            preg_match($variable_path, $path, $matched);

            if ( ! empty($matched)) {
                // get params
                array_shift($matched);
                $path_info['params'] = $matched;
                // save variable path
                $path_info['callback'] = self::$_variable_map_tree[$variable_path][$method];
                // save middleware symbols
                $path_info['middleware'] = self::$_middleware_map_tree[$variable_path][$method];
                // stop find
                break;
            }
        }

        return empty($matched) ? NULL : $path_info;
    }

    /**
     * To avoid memory leaks, clear variable route cache with LRU when data out of array length.
     *
     * @param  string  $value
     * @return void
     */
    protected static function _variableRouteCacheControl($value)
    {
        // is value in cache index list?
        if (FALSE !== ($index = array_search($value, self::$_variable_route_cache_index))) {
            // if value is tail, do nothing
            if ($index == (count(self::$_variable_route_cache_index) - 1)) {
                return;
            }
            // unset old value
            unset(self::$_variable_route_cache_index[$index]);
            // reset array index
            self::$_variable_route_cache_index = array_values(self::$_variable_route_cache_index);
        }
        // push value to tail
        array_push(self::$_variable_route_cache_index, $value);
        // cache index list out of range?
        if (count(self::$_variable_route_cache_index) > self::$_variable_route_cache_limit) {
            // remove head value
            $remove_value = array_shift(self::$_variable_route_cache_index);
            // unset route cache, free memory
            unset(self::$_variable_route_cache[$remove_value]);
        }
    }

    /**
     * set map tree.
     *
     * @param  string  $method
     * @param  string  $path
     * @param  mixed  $content string or Closure
     * @return void
     */
    protected static function _setMapTree($method, $path, $content)
    {
        $path     = self::_pathParse(self::$_filter['prefix'].$path);
        $callback = is_string($content) ?
                    self::_namespaceParse('\\'.self::$_filter['namespace'].$content) : $content;

        if (self::_isVariableRoute($path)) { // is variable route
            $path = self::_variablePathReplace($path);
            self::$_variable_map_tree[$path][strtoupper($method)] = $callback;
        } else { // is usual route
            self::$_map_tree[$path][strtoupper($method)] = $callback;
        }

        self::$_middleware_map_tree[$path][strtoupper($method)] = self::$_filter['middleware'];
    }

    /**
     * get redirect url
     *
     * @param  string  $path
     * @param  array  $param
     * @return string
     */
    protected static function _getRedirectUrl($path, $param)
    {
        $base_url = rtrim(Config::get('app.base_url'), '/');
        $path = self::_pathParse($path);
        $url = $base_url.$path.'?'.http_build_query($param);

        return $url;
    }

    /**
     * check middlewares.
     *
     * @param  WorkerF\Http\Requests $request
     * @param  array $middleware_symbols
     * @return mixed
     * @throws \InvalidArgumentException
     */
    protected static function _checkMiddleware(Requests $request, $middleware_symbols)
    {
        // get all route middlewares
        $route_middlewares = Config::get('middleware.route');
        // get current request route middlewares
        $request_middlewares = [];
        foreach ($middleware_symbols as $middleware_symbol) {
            // if middleware_symbol is not in route_middlewares
            if ( ! array_key_exists($middleware_symbol, $route_middlewares)) {
                throw new \InvalidArgumentException("route middleware $middleware_symbol is not exists!");
            }
            $request_middlewares[] = $route_middlewares[$middleware_symbol];
        }
        // run middleware flow
        return Middleware::run($request_middlewares, $request);
    }

    /**
     * run dispatch.
     *
     * @param WorkerF\Http\Requests $request
     * @param mixed $callback string or Closure
     * @param array $middleware_symbols
     * @param array $params
     * @return mixed
     * @throws \LogicException
     * @throws \BadMethodCallException
     * @throws \InvalidArgumentException
     */
    protected static function _runDispatch(Requests $request, $callback, $middleware_symbols, $params = [])
    {
        // check route middlewares
        $request = self::_checkMiddleware($request, $middleware_symbols);
        // if middlewares check is not passed
        if ( ! ($request instanceof Requests)) {
            return $request;
        }

        // is class
        if (is_string($callback)) {
            // syntax check
            if ( ! preg_match('/^[a-zA-Z0-9_\\\\]+@[a-zA-Z0-9_]+$/', $callback)) {
                throw new \LogicException("Please use controller@method define callback");
            }
            // get controller method info
            $controller = explode('@', $callback);
            list($class, $method) = [$controller[0], $controller[1]];
            // class methods exist ?
            if ( ! class_exists($class) || ! method_exists($class, $method)) {
                $e = new \BadMethodCallException("Class@method: $callback is not found!");
                $e->httpCode = 404;
                throw $e;
            }
            // call method
            return IOCContainer::run($class, $method, $params);
        }
        // is callback
        if (is_callable($callback)) {
            // call function
            return call_user_func_array($callback, $params);
        }
    }

    /**
     * Set variable route cache length limit.
     *
     * @param  int  $value
     * @return void
     */
    public static function setVariableRouteCacheLimit($value)
    {
        if ( ! (is_numeric($value) && $value > 0)) {
            throw new \InvalidArgumentException("The route cache limit need a numeric greater than 0 !");
        }
        self::$_variable_route_cache_limit = $value;
    }

    /**
     * dispatch route.
     *
     * @param WorkerF\Http\Requests $request
     * @return mixed
     * @throws \LogicException
     * @throws \BadMethodCallException
     * @throws \InvalidArgumentException
     */
    public static function dispatch(Requests $request)
    {
        // get request param
        $path   = self::_pathParse($request->path());
        $method = $request->method();

        // router exist or not
        if (array_key_exists($path, self::$_map_tree) &&
           array_key_exists($method, self::$_map_tree[$path])
        ) {
            // get current request route middleware symbols
            $middleware_symbols = self::$_middleware_map_tree[$path][$method];
            // get callback info
            $callback = self::$_map_tree[$path][$method];

            return self::_runDispatch($request, $callback, $middleware_symbols);
        }

        // route in variable route cache or not
        if (array_key_exists($path, self::$_variable_route_cache) &&
           array_key_exists($method, self::$_variable_route_cache[$path])
        ) {
            // get variable route info
            $callback           = self::$_variable_route_cache[$path][$method]['callback'];
            $params             = self::$_variable_route_cache[$path][$method]['params'];
            $middleware_symbols = self::$_variable_route_cache[$path][$method]['middleware'];
            // dispatch variable route
            return self::_runDispatch($request, $callback, $middleware_symbols, $params);
        }

        // router is variable router or not
        if (NULL !== ($path_info = self::_variablePathParse($path, $method))) {
            // get variable route info
            $callback           = $path_info['callback'];
            $params             = $path_info['params'];
            $middleware_symbols = $path_info['middleware'];
            // clear route cache with LRU if data out of range
            self::_variableRouteCacheControl($path);
            // save variable route to cache
            self::$_variable_route_cache[$path][$method]['callback']   = $callback;
            self::$_variable_route_cache[$path][$method]['params']     = $params;
            self::$_variable_route_cache[$path][$method]['middleware'] = $middleware_symbols;
            // dispatch variable route
            return self::_runDispatch($request, $callback, $middleware_symbols, $params);
        }

        // route is not found
        $e = new \LogicException("route rule path: $path <==> method : $method is not set!");
        $e->httpCode = 404;
        throw $e;
    }

    /**
     * redirect. warning: only for get method
     *
     * @param  string  $path
     * @param  array  $param
     * @return \Closure
     */
    public static function redirect($path, $param = [])
    {
        $url = self::_getRedirectUrl($path, $param);

        return Response::redirect($url);
    }

}


<?php
namespace WorkerF\DB;

use WorkerF\DB\ConnectException;
use WorkerF\DB\Drivers\Mysql;
use WorkerF\DB\Drivers\Pgsql;
use WorkerF\DB\Drivers\Sqlite;
/**
 * DB.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class DB
{
    /**
     * connections.
     *
     * @var array
     */
    protected static $_connections = [];

    /**
     * init db connections.
     *
     * @param  array $db_confs
     * @return void
     * @throws WorkerF\DB\ConnectException
     */
    public static function init(array $db_confs)
    {
        // connect database
        foreach ($db_confs as $con_name => $db_conf) {
            try {
                switch (strtolower($db_conf['driver'])) {
                    case 'mysql':
                        self::$_connections[$con_name] = new Mysql($db_conf);
                        break;
                    case 'pgsql':
                        self::$_connections[$con_name] = new Pgsql($db_conf);
                        break;
                    case 'sqlite':
                        self::$_connections[$con_name] = new Sqlite($db_conf);
                        break;
                    default:
                        break;
                }

            } catch (\Exception $e) {
                $msg = "Database connect fail, check your database config for connection '$con_name' \n".$e->getMessage();
                throw new ConnectException($msg);
            }
        }
    }

    /**
     * get db connection.
     *
     * @param string $con_name
     * @return object
     */
    public static function connection($con_name)
    {
        return self::$_connections[$con_name];
    }
}


<?php
namespace WorkerF\DB\Drivers;
use PDO;
use PDOException;
use WorkerF\DB\Drivers\PDODriver;

/**
 * Mysql Driver
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Mysql extends PDODriver
{
    /**
     * escape symbol
     *
     * @var array
     */
    protected static $_quote_symbol = '`';

    /**
     * operators
     *
     * @var array
     */
    protected $_operators = [
      '=', '<', '>', '<=', '>=', '<>', '!=', '<=>',
      'like', 'not like', 'like binary', 'rlike', 'regexp', 'not regexp',
      '&', '|', '^', '<<', '>>',
    ];

    /**
     * create a PDO instance
     *
     * @return  void
     * @throws  \PDOException
     */
    protected function _connect()
    {
        extract($this->_config, EXTR_SKIP);

        $dsn = isset($unix_socket) ?
               'mysql:unix_socket='.$unix_socket.';dbname='.$dbname :
               'mysql:dbname='.$dbname.';host='.$host.(isset($port) ? ';port='.$port : '');

        $options = isset($options) ? $options + $this->_options : $this->_options;

        try {

            $this->_pdo = new PDO($dsn, $user, $password, $options);

            // charset set
            if (isset($charset)) {
                $this->_pdo->prepare("set names $charset ".(isset($collation) ? " collate '$collation'" : ''))->execute();
            }
            // timezone
            if (isset($timezone)) {
                $this->_pdo->prepare("set time_zone='$timezone'")->execute();
            }
            // strict mode
            if (isset($strict)) {
                if ($strict) {
                    $this->_pdo->prepare("set session sql_mode='STRICT_ALL_TABLES'")->execute();
                } else {
                    $this->_pdo->prepare("set session sql_mode=''")->execute();
                }
            }
        } catch (PDOException $e) {
            throw $e;
        }
    }

}


<?php
namespace WorkerF\Tool;

/**
 * DotArr, use "a.b.c" mode set\get config data.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class DotArr
{
    /**
     * Set an array item to a given value using "dot" notation.
     *
     * If no key is given to the method, the entire array will be replaced.
     *
     * @param  array   $array
     * @param  string  $key
     * @param  mixed   $value
     * @return array
     */
    public static function dotSet(&$array, $key, $value)
    {
        if (is_null($key)) {
            return $array = $value;
        }

        $keys = explode('.', $key);

        while (count($keys) > 1) {
            $key = array_shift($keys);
            if ( ! isset($array[$key]) || ! is_array($array[$key])) {
                $array[$key] = [];
            }

            $array = &$array[$key];
        }

        $array[array_shift($keys)] = $value;

        return $array;
    }

    /**
     * Get an item from an array using "dot" notation.
     *
     * @param  array   $array
     * @param  string  $key
     * @param  mixed   $default
     * @return mixed
     */
    public static function dotGet($array, $key, $default = NULL)
    {
        if (is_null($key)) {
            return $array;
        }

        if (isset($array[$key])) {
            return $array[$key];
        }

        foreach (explode('.', $key) as $segment) {
            if ( ! is_array($array) || ! array_key_exists($segment, $array)) {
                return $default;
            }

            $array = $array[$segment];
        }

        return $array;
    }

    /**
     * Check if an item exists in an array using "dot" notation.
     *
     * @param  array   $array
     * @param  string  $key
     * @return bool
     */
    public static function dotHas($array, $key)
    {
        if (empty($array) || is_null($key)) {
            return FALSE;
        }

        if (array_key_exists($key, $array)) {
            return TRUE;
        }

        foreach (explode('.', $key) as $segment) {
            if (! is_array($array) || ! array_key_exists($segment, $array)) {
                return FALSE;
            }

            $array = $array[$segment];
        }

        return TRUE;
    }
}


<?php
namespace WorkerF\WorkerSocket;

use Workerman\Protocols\Http;
use Workerman\Protocols\HttpCache;

/**
 * WorkerHttp.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class WorkerHttp
{
    /**
     * create http response header.
     *
     * @param  mixed  $header array or string
     * @return void
     */
    public static function header($headers)
    {
        if (is_array($headers)) {
            // if pass array
            foreach ($headers as $header) {
                if (FALSE === Http::header($header)) {
                    throw new \InvalidArgumentException("Header $header is invalid!");
                }
            }
            return;
        }
        // pass string
        if (FALSE === Http::header($headers)) {
            throw new \InvalidArgumentException("Header $headers is invalid!");
        }
    }

    /**
     * get http response header.
     *
     * @param  string  $key
     * @return string
     */
    public static function getHeader($key)
    {
        if ( ! array_key_exists($key, HttpCache::$header)) {
            return NULL;
        }

        return HttpCache::$header[$key];
    }
}


<?php
namespace WorkerF\DB\Drivers;
use PDO;
use PDOException;
use WorkerF\DB\Drivers\PDODriver;

/**
 * Mysql Driver
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Pgsql extends PDODriver
{

    /**
     * escape symbol
     *
     * @var array
     */
    protected static $_quote_symbol = '"';

    /**
     * operators
     *
     * @var array
     */
    protected $_operators = [
      '=', '<', '>', '<=', '>=', '<>', '!=',
      'like', 'not like', 'ilike', 'similar to', 'not similar to',
      '&', '|', '#', '<<', '>>',
    ];

    /**
     * The default PDO connection options.
     *
     * @var array
     */
    protected $_options = [
        PDO::ATTR_CASE => PDO::CASE_NATURAL,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
        PDO::ATTR_STRINGIFY_FETCHES => false,
    ];

    /**
     * create a PDO instance
     *
     * @return  void
     * @throws  \PDOException
     */
    protected function _connect()
    {
        extract($this->_config, EXTR_SKIP);

        $dsn = 'pgsql:dbname='.$dbname.
               (isset($host) ? ';host='.$host : '').
               (isset($port) ? ';port='.$port : '').
               (isset($sslmode) ? ';sslmode='.$sslmode : '');

        $options = isset($options) ? $options + $this->_options : $this->_options;

        try {

            $this->_pdo = new PDO($dsn, $user, $password, $options);

            // charset set
            if (isset($charset)) {
                $this->_pdo->prepare("set names '$charset'")->execute();
            }
            // timezone
            if (isset($timezone)) {
                $this->_pdo->prepare("set time zone '$timezone'")->execute();
            }
            // set schema path
            if (isset($schema)) {
                $this->_pdo->prepare("set search_path to $schema")->execute();
            }
            // set application name
            if (isset($application_name)) {
                $this->_pdo->prepare("set application_name to '$applicationName'")->execute();
            }
        } catch (PDOException $e) {
            throw $e;
        }
    }

    /**
     * get last insert ID for postgresql
     *
     * @param  array $data
     * @return  null/int
     * @throws  \PDOException
     */
    public function insertGetLastId(array $data)
    {
        // create build str
        $field_str = '';
        $value_str = '';
        foreach ($data as $key => $value) {
            $field_str .= ' '.self::_wrapRow($key).',';
            $plh = self::_getPlh();
            $this->_bind_params[$plh] = $value;
            $value_str .= ' '.$plh.',';
        }

        $field_str = rtrim($field_str, ',');
        $value_str = rtrim($value_str, ',');

        $this->_insert_str = ' ('.$field_str.') VALUES ('.$value_str.') RETURNING id ';
        // execute
        $this->_buildInsert();
        $this->_execute();
        $result = $this->_pdoSt->fetch(PDO::FETCH_ASSOC);

        return $result['id'];
    }

}


<?php
namespace WorkerF\DB\Drivers;
use PDO;
use PDOException;
use WorkerF\DB\Drivers\PDODriver;

/**
 * Mysql Driver
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Sqlite extends PDODriver
{
    /**
     * escape symbol
     *
     * @var array
     */
    protected static $_quote_symbol = '"';

    /**
     * operators
     *
     * @var array
     */
    protected $_operators = [
      '=', '<', '>', '<=', '>=', '<>', '!=',
      'like', 'not like', 'ilike',
      '&', '|', '<<', '>>',
    ];

    /**
     * create a PDO instance
     *
     * @return  void
     * @throws  \PDOException
     * @throws  \InvalidArgumentException
     */
    protected function _connect()
    {
        extract($this->_config, EXTR_SKIP);

        if ($dbname == ':memory:') {
            $dsn = 'sqlite::memory:';
        } else {
            $path = realpath($dbname);

            if ($path === FALSE) {
                throw new \InvalidArgumentException("Database $dbname does not exist.");
            }
            $dsn = 'sqlite:'.$path;
        }

        $options = isset($options) ? $options + $this->_options : $this->_options;

        try {

            $this->_pdo = new PDO($dsn, '', '', $options);

        } catch (PDOException $e) {
            throw $e;
        }
    }

}


<?php
namespace WorkerF;

use ReflectionClass;

/**
 * Dependency injection IOC Container.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class IOCContainer
{
    /**
     * singleton instances.
     *
     * @var array
     */
    protected static $_singleton = [];

    /**
     * create Dependency injection params.
     *
     * @param  array $params
     * @return array
     */
    protected static function _getDiParams(array $params)
    {
        $di_params = [];
        foreach ($params as $param) {
            $class = $param->getClass();
            if ($class) {
                // check dependency is a singleton instance or not
                $singleton = self::getSingleton($class->name);
                $di_params[] = $singleton ? $singleton : self::getInstance($class->name);
            }
        }

        return $di_params;
    }

    /**
     * set a singleton instance.
     *
     * @param  object $instance
     * @param  string $name
     * @return void
     * @throws \InvalidArgumentException
     */
    public static function singleton($instance, $name = NULL)
    {
        if ( ! is_object($instance)) {
            throw new \InvalidArgumentException("Object need!");
        }

        $class_name = $name == NULL ? get_class($instance) : $name;

        // singleton not exist, create
        if ( ! array_key_exists($class_name, self::$_singleton)) {
            self::$_singleton[$class_name] = $instance;
        }
    }

    /**
     * get a singleton instance.
     *
     * @param  string $class_name
     * @return mixed object or NULL
     */
    public static function getSingleton($class_name)
    {
        return array_key_exists($class_name, self::$_singleton) ?
                self::$_singleton[$class_name] : NULL;
    }

    /**
     * unset a singleton instance.
     *
     * @param  string $class_name
     * @return void
     */
    public static function unsetSingleton($class_name)
    {
        self::$_singleton[$class_name] = NULL;
    }

    /**
     * register class, instantiate class, set instance to singleton.
     *
     * @param  string $abstract abstract class name
     * @param  string $concrete concrete class name, if NULL, use abstract class name
     * @return void
     */
    public static function register($abstract, $concrete = NULL)
    {
        if ($concrete == NULL) {
            $instance = self::getInstance($abstract);
            self::singleton($instance);
        } else {
            $instance = self::getInstance($concrete);
            self::singleton($instance, $abstract);
        }
    }

    /**
     * get Instance from reflection info.
     *
     * @param  string  $class_name
     * @return object
     */
    public static function getInstance($class_name)
    {
        // get class reflector
        $reflector = new ReflectionClass($class_name);
        // get constructor
        $constructor = $reflector->getConstructor();
        // create di params
        $di_params = $constructor ? self::_getDiParams($constructor->getParameters()) : [];
        // create instance
        return $reflector->newInstanceArgs($di_params);
    }

    /**
     * get Instance, if instance is not singleton, set it to singleton.
     *
     * @param  string  $class_name
     * @return object
     */
    public static function getInstanceWithSingleton($class_name)
    {
        // is a singleton instance?
        if (NULL != ($instance = self::getSingleton($class_name))) {
            return $instance;
        }

        $instance = self::getInstance($class_name);
        self::singleton($instance);

        return $instance;
    }

    /**
     * run class method.
     *
     * @param  string $class_name
     * @param  string $method
     * @param  array  $params
     * @return mixed
     * @throws \BadMethodCallException
     */
    public static function run($class_name, $method, $params = [])
    {
        // class exist ?
        if ( ! class_exists($class_name)) {
            throw new \BadMethodCallException("Class $class_name is not found!");
        }
        // method exist ?
        if ( ! method_exists($class_name, $method)) {
            throw new \BadMethodCallException("undefined method $method in $class_name !");
        }
        // create instance
        $instance = self::getInstance($class_name);
        /******* method Dependency injection *******/
        // get class reflector
        $reflector = new ReflectionClass($class_name);
        // get method
        $reflectorMethod = $reflector->getMethod($method);
        // create di params
        $di_params = self::_getDiParams($reflectorMethod->getParameters());
        // run method
        return call_user_func_array([$instance, $method], array_merge($di_params, $params));
    }
}


<?php
namespace WorkerF;
use Workerman\Connection\TcpConnection;
use WorkerF\Http\Requests;
use WorkerF\Http\Response;
use WorkerF\Http\Route;
use WorkerF\Http\Middleware;
use WorkerF\IOCContainer;
use WorkerF\Config;
use WorkerF\Error;
use WorkerF\DB\DB;
use WorkerF\DB\Redis;
use WorkerF\Exceptions\ExceptionHandler;

/**
 * App.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class App
{
    /**
     * run http app.
     *
     * @param  Workerman\Connection\TcpConnection $con
     * @return void
     * @throws \LogicException
     * @throws \BadMethodCallException
     * @throws \InvalidArgumentException
     * @throws \PDOException
     */
    public static function run(TcpConnection $con)
    {
        try {
            // build config
            $conf = Config::get('app');
            // get request
            $request = new Requests();

            // check global middlewares
            $global_middlerwares = Config::get('middleware.global');
            $result = Middleware::run($global_middlerwares, $request);
            // middlewares check passed?
            if ($result instanceof Requests) {
                // run dispatch
                $result = Route::dispatch($result);
            }

        } catch (\Exception $e) {
            // Handle Exception
            $exceptionHandler = IOCContainer::getInstanceWithSingleton(ExceptionHandler::class);

            $result = $exceptionHandler->handle($e);

        } finally {
            // return Response data
            $response = Response::bulid($result, $conf);
            $con->send($response);
        }
    }

    /**
     * Initialize some devices like redis \ database ...
     *
     * @return void
     * @throws WorkerF\DB\ConnectException
     */
    public static function init()
    {
        try {
            // register class
            self::register();
            // init database
            DB::init(Config::get('database.db_con'));
            // init redis
            Redis::init(Config::get('database.redis'));

        } catch (\Exception $e) {
            Error::printError($e->getMessage());
        }
    }

    /**
     * register class
     *
     * @return void
     */
    public static function register()
    {
        // register middlewares
        $registers = Config::get('app.registers');

        if ( ! empty($registers)) {
            foreach ($registers as $register) {
                IOCContainer::register($register);
            }
        }
    }

}


<?php
namespace WorkerF\Tool;

use InvalidArgumentException;
use Closure;

/**
 * Pipeline, pipe mode.
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Pipeline
{
    /**
     * Pipes.
     *
     * @var array
     */
    protected $_pipes = [];

    /**
     * set pipes, array mode.
     *
     * @param $pipes
     * @throws \InvalidArgumentException
     */
    public function __construct($pipes = [])
    {
        foreach ($pipes as $pipe) {
            if (FALSE === is_callable($pipe)) {
                throw new InvalidArgumentException('All pipes should be callable.');
            }
        }
        $this->_pipes = $pipes;
    }

    /**
     * set pipes, single mode.
     *
     * @param $pipe
     * @return self
     * @throws \InvalidArgumentException
     */
    public function pipe($pipe)
    {
        if (FALSE === is_callable($pipe)) {
            throw new InvalidArgumentException('pipe should be callable.');
        }

        $this->_pipes[] = $pipe;

        return $this;
    }

    /**
     * process pipeline flow, when payload passed as a closure, stop pipeline flow.
     *
     * @param $payload
     * @return mixed
     */
    public function flow($payload)
    {
        foreach ($this->_pipes as $pipe) {
            // process pipe
            $payload = call_user_func($pipe, $payload);

            if ($payload instanceOf Closure) {
                // if payload is a closure, stop pipeline flow
                return call_user_func($payload);
            }
        }

        return $payload;
    }
}


<?php
namespace WorkerF\DB;

use WorkerF\DB\ConnectException;
use Predis\Client;
use Closure;
/**
 * Redis. reference from Laravel Redis, based on Predis
 *
 * @author MirQin https://github.com/wazsmwazsm
 */
class Redis
{
    /**
     * clients.
     *
     * @var array
     */
    protected static $_clients;

    /**
     * init redis clients.
     *
     * @param  array $rd_confs
     * @return void
     * @throws WorkerF\DB\ConnectException
     */
    public static function init(array $rd_confs)
    {
        // create redis init params
        $cluster = $rd_confs['cluster'];
        $options = (array) $rd_confs['options'];
        $servers = $rd_confs['rd_con'];
        // get clients
        self::$_clients = $cluster ?
            self::createAggregateClient($servers, $options) :
            self::createSingleClients($servers, $options);
        // check redis connect
        foreach (self::$_clients as $con_name => $client) {
            try {
                $client->connect();

            } catch (\Exception $e) {
                $msg = "Redis connect fail, check your redis config for connection '$con_name'. \n".$e->getMessage();
                throw new ConnectException($msg);
            }
        }
    }

    /**
     * Get a specific Redis connection instance.
     *
     * @param  string  $con
     * @return \Predis\ClientInterface|null
     */
    public static function connection($con)
    {
        return self::$_clients[$con];
    }

    /**
     * Run a command against the Redis database.
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     */
    public static function cmd($method, array $parameters = [])
    {
        return call_user_func_array([self::$_clients['default'], $method], $parameters);
    }

    /**
     * Create a new aggregate client supporting sharding.
     *
     * @param  array  $servers
     * @param  array  $options
     * @return array
     */
    public static function createAggregateClient(array $servers, array $options = [])
    {
        return ['default' => new Client(array_values($servers), $options)];
    }

    /**
     * Create an array of single connection clients.
     *
     * @param  array  $servers
     * @param  array  $options
     * @return array
     */
    public static function createSingleClients(array $servers, array $options = [])
    {
        $clients = [];

        foreach ($servers as $key => $server) {
            $clients[$key] = new Client($server, $options);
        }

        return $clients;
    }

    /**
     * Subscribe to a set of given channels for messages.
     * 'read_write_timeout' => 0 to solve the timeout problem
     *
     * @param  array|string  $channels
     * @param  \Closure  $callback
     * @param  string  $connection
     * @param  string  $method
     * @return void
     */
    public static function subscribe($channels, Closure $callback, $connection = 'default', $method = 'subscribe')
    {
        $loop = self::$_clients[$connection]->pubSubLoop();

        call_user_func_array([$loop, $method], (array) $channels);

        // loop blocking, start listen redis publish messages
        foreach ($loop as $message) {
            if ($message->kind === 'message' || $message->kind === 'pmessage') {
                // get publish message
                call_user_func($callback, $message->payload, $message->channel);
            }
        }
        // unset Predis\PubSub\Consumer iterator
        unset($loop);
    }

    /**
     * Subscribe to a set of given channels with wildcards.
     *
     * @param  array|string  $channels
     * @param  \Closure  $callback
     * @param  string  $connection
     * @return void
     */
    public static function psubscribe($channels, Closure $callback, $connection = 'default')
    {
        return self::subscribe($channels, $callback, $connection, 'psubscribe');
    }

    /**
     * call method (predis methods).
     *
     * @param  string  $method
     * @param  array  $params
     * @return void
     */
    public static function __callstatic($method, $params)
    {
        return call_user_func_array([self::$_clients['default'], $method], $params);
    }

}