<?php

namespace Bvipul\Generator\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Access\Permission\Permission;
use Bvipul\Generator\Repositories\ModuleRepository;

/**
 * Class ModuleController.
 *
 * @author Vipul Basapati <basapativipulkumar@gmail.com | https://github.com/bvipul>
 */
class ModuleController extends Controller
{
    public $repository;
    public $generator;
    public $event_namespace = 'app\\Events\\Backend\\';

    /**
     * Constructor.
     *
     * @param ModuleRepository $repository
     */
    public function __construct(ModuleRepository $repository)
    {
        $this->repository = $repository;
        $this->generator = new Generator();
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        return view('generator::index');
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create(Request $request)
    {
        return view('generator::create')
            ->with('model_namespace', $this->generator->getModelNamespace())
            ->with('request_namespace', $this->generator->getRequestNamespace())
            ->with('controller_namespace', $this->generator->getControllerNamespace())
            ->with('event_namespace', $this->event_namespace)
            ->with('repo_namespace', $this->generator->getRepoNamespace())
            ->with('route_path', $this->generator->getRoutePath())
            ->with('view_path', $this->generator->getViewPath());
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->generator->initialize($request->all());
        $this->generator->createMigration();
        $this->generator->createModel();
        $this->generator->createRequests();
        $this->generator->createResponses();
        $this->generator->createRepository();
        $this->generator->createController();
        $this->generator->createTableController();
        $this->generator->createRouteFiles();
        $this->generator->insertToLanguageFiles();
        $this->generator->createViewFiles();
        $this->generator->createEvents();

        // Creating the Module
        $this->repository->create($request->all(), $this->generator->getPermissions());

        return redirect()->route('admin.modules.index')->withFlashSuccess('Module Generated Successfully!');
    }

    /**
     * Checking the path for a file if exists.
     *
     * @param Request $request
     */
    public function checkNamespace(Request $request)
    {
        if (isset($request->path)) {
            $path = $this->parseModel($request->path);
            $path = base_path(trim(str_replace('\\', '/', $request->path)), '\\');
            $path = str_replace('App', 'app', $path);
            if (file_exists($path.'.php')) {
                return response()->json((object) [
                    'type'    => 'error',
                    'message' => 'File exists Already',
                ]);
            } else {
                return response()->json((object) [
                    'type'    => 'success',
                    'message' => 'File can be generated at this location',
                ]);
            }
        } else {
            return response()->json((object) [
                'type'    => 'error',
                'message' => 'Please provide some value',
            ]);
        }
    }

    /**
     * Checking if the table exists.
     *
     * @param Request $request
     */
    public function checkTable(Request $request)
    {
        if ($request->table) {
            if (Schema::hasTable($request->table)) {
                return response()->json((object) [
                    'type'    => 'error',
                    'message' => 'Table exists Already',
                ]);
            } else {
                return response()->json((object) [
                    'type'    => 'success',
                    'message' => 'Table Name Available',
                ]);
            }
        } else {
            return response()->json((object) [
                'type'    => 'error',
                'message' => 'Please provide some value',
            ]);
        }
    }

    /**
     * Checking if the table exists.
     *
     * @param Request $request
     */
    public function checkRoute(Request $request)
    {
        if ($request->table) {
            if (Schema::hasTable($request->table)) {
                return response()->json((object) [
                    'type'    => 'error',
                    'message' => 'Table exists Already',
                ]);
            } else {
                return response()->json((object) [
                    'type'    => 'success',
                    'message' => 'Table Name Available',
                ]);
            }
        } else {
            return response()->json((object) [
                'type'    => 'error',
                'message' => 'Please provide some value',
            ]);
        }
    }

    /**
     * Get the fully-qualified model class name.
     *
     * @param string $model
     *
     * @return string
     */
    public function parseModel($model)
    {
        if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) {
            throw new InvalidArgumentException('Name contains invalid characters.');
        }

        $model = trim(str_replace('/', '\\', $model), '\\');

        return $model;
    }

    public function checkPermission(Request $request)
    {
        $permission = $request->permission;

        if ($permission) {
            $per = Permission::where('name', $permission)->first();

            if ($per) {
                return response()->json((object) [
                    'type'    => 'success',
                    'message' => 'Permission Exists',
                ]);
            } else {
                return response()->json((object) [
                    'type'    => 'error',
                    'message' => 'Permission does not exists',
                ]);
            }
        } else {
            return response()->json((object) [
                'type'    => 'error',
                'message' => 'Please provide some value',
            ]);
        }
    }
}


<?php

namespace M6Web\Component\Redis;

use Predis;

/**
 * Class Multi
 * Allow you to send a unique command on multiple redis
 *
 * @package M6Web\Component\Redis
 */
class Multi extends Manager
{

    protected $selectedRedis = array();

    protected $multiRedis = false;

    /**
     * constructor
     *
     * @param array $params
     *
     * @throws Exception
     */
    public function __construct($params)
    {
        $this->setCurrentDb(1); // default hardcoded choice for the db
        if (isset($params['compress']) and ($params['compress'] === true)) {
            throw new Exception("cant use the compress option in this class");
        }
        if (isset($params['namespace'])) {
            throw new Exception("cant use the namespace option in this class");
        }

        return parent::__construct($params);
    }

    /**
     * select one random server
     *
     * @throws Exception
     * @return $this
     */
    public function onOneRandomServer()
    {
        $keys = array_keys($this->getServerConfig());

        do {
            $randServerRank = array_rand($keys);
            if ($redis = $this->getRedisFromServerConfig($keys[$randServerRank])) {
                $this->selectedRedis = array($keys[$randServerRank] => $redis);
            } else {
                unset($keys[$randServerRank]);
            }
        } while (!empty($keys) && empty($this->selectedRedis));

        if (empty($this->selectedRedis)) {
            throw new Exception("Can't connect to a random redis server");
        }

        $this->multiRedis = false;

        return $this;
    }

    /**
     * select all the servers
     *
     * @param bool $strict
     *
     * @throws Exception
     * @return $this
     */
    public function onAllServer($strict = true)
    {
        foreach ($this->getServerConfig() as $idServer => $config) {
            if ($redis = $this->getRedisFromServerConfig($idServer)) {
                $this->selectedRedis[$idServer] = $redis;
            } else {
                if ($strict) {
                    throw new Exception('cant connect to redis '.$idServer);
                }
            }
        }

        $this->multiRedis = true;

        return $this;
    }

    /**
     * select one server
     *
     * @param string  $idServer
     * @param boolean $strict
     *
     * @throws Exception
     * @return $this
     */
    public function onOneServer($idServer, $strict = true)
    {
        $redisList = $this->getServerConfig();

        // The server must exist..
        if (array_key_exists($idServer, $redisList)) {
            // and must be available
            if ($redis = $this->getRedisFromServerConfig($idServer)) {
                $this->selectedRedis[$idServer] = $redis;
            } else {
                if ($strict) {
                    throw new Exception('cant connect to redis ' . $idServer);
                }
            }
        } else {
            throw new Exception('unknown redis '.$idServer);
        }

        $this->multiRedis = false;

        return $this;
    }

    /**
     * select one server according to the key
     *
     * @param string $key cache key
     *
     * @return $this
     * @throws Exception when no redis server available
     */
    public function onOneKeyServer($key)
    {
        $this->selectedRedis[] = $this->getRedis($key);
        $this->multiRedis = false;

        return $this;
    }

    /**
     * magic method to the \Redis() proxy
     *
     * @param string $name      method name
     * @param array  $arguments method arguments
     *
     * @throws Exception
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        if (empty($this->selectedRedis)) {
            throw new Exception("please call onOneRandomServer, onOneServer or onAllServer before ".__METHOD__);
        }

        // Call to all available servers
        if ($this->multiRedis) {
            $toReturn = [];
            foreach ($this->selectedRedis as $idServer => $redis) {
                    $toReturn[$idServer] = $this->callRedisCommand($redis, $name, $arguments);
            }
        } else {
            // Only one server
            $toReturn = $this->callRedisCommand(array_shift($this->selectedRedis), $name, $arguments);
        }

        // reinit selected Redis
        $this->selectedRedis = array();

        return $toReturn;
    }

    /**
     * call a redis command
     *
     * @param PredisProxy    $redis
     * @param string         $name
     * @param array          $arguments
     *
     * @throws Exception
     * @return mixed
     */
    protected function callRedisCommand(PredisProxy $redis, $name, $arguments)
    {
        $start = microtime(true);
        try {
            $return = call_user_func_array(array($redis, $name), $arguments);
            $this->notifyEvent($name, $arguments, microtime(true) - $start);
        } catch (Predis\PredisException $e) {
            throw new Exception("Error calling the method ".$name." : ".$e->getMessage());
        }

        return $return;
    }
}


<?php
/**
 * @author Olivier Mansour
 *
 * strongly depend on predis
 *
 */

namespace M6Web\Component\Redis;

use Predis;

/**
 * Manager for Redis classes
 */
abstract class Manager
{

    /**
     * server default timeout
     * @var float
     */
    protected $timeout = 0.2;

    /**
     * read write default timeout
     * @var float
     */
    protected $read_write_timeout = 0.2;

    /**
     * array of server configuration
     * @var array
     */
    protected $serverConfig = null;

    /**
     * array of Redis object
     * @var array
     */
    protected $aliveRedis = array();

    /**
     * array of Redis object
     * @var array
     */
    protected $deadRedis = array();

    /**
     * db (as redis understand it)
     * @var integer
     */
    protected $currentDb;

    /**
     * do I use the compression
     * @var bool
     */
    private $compress;

    /**
     * event dispatcher
     * @var Object
     */
    protected $eventDispatcher = null;

    /**
     * class of the event notifier
     * @var string
     */
    protected $eventClass = null;

    /**
     * number of reconnect if a command fail (passed to predisProxy)
     * @var int
     */
    protected $reconnect = 0;

    /**
     * name of the event
     * @var string
     */
    protected $eventName = 'redis.command';

    /**
     * constructor
     * $params = array(
     * 'timeout' => 2,
     * 'compress' => true,
     * 'reconnect' => 2
     * 'server_config' = array(
     *   'php50' => array (
     *       'ip' => '193.22.143.110',
     *       'port' => 6379,
     *       ));
     *
     * @param array $params Manager params
     *
     * @return \M6Web\Component\Redis\Manager
     */
    public function __construct($params)
    {
        $this->init($params);

        return $this;
    }

    /**
     * set the current db
     *
     * @param integer $v
     *
     * @throws Exception
     * @return object DB
     */
    public function setCurrentDb($v)
    {
        if (!is_int($v)) {
            throw new Exception("please describe the db as an integer ^^");
        }
        if ($v == Cache::CACHE) {
            throw new Exception("cant use ".Cache::CACHE." in class ".__CLASS__);
        }
        $this->currentDb = $v;

        return $this;
    }

    /**
     * get the current db
     * @throws Exception
     * @return string|int
     */
    public function getCurrentDb()
    {
        if (is_null($this->currentDb)) {
            throw new Exception("currentDb cant be null");
        }

        return $this->currentDb;
    }

    /**
     * Notify an event to the event dispatcher
     * @param string $command   The command name
     * @param array  $arguments args of the command
     * @param int    $time      exec time
     *
     * @return \M6Web\Component\Redis\Manager
     */
    public function notifyEvent($command, $arguments, $time = 0)
    {
        if ($this->eventDispatcher) {
            $className = $this->eventClass;
            $event = new $className();
            $event->setCommand($command);
            $event->setExecutionTime($time);
            $event->setArguments($arguments);
            $this->eventDispatcher->dispatch($this->eventName, $event);
        }

        return $this;
    }

    /**
     * Set an event dispatcher to notify redis command
     * @param Object $eventDispatcher The eventDispatcher object, which implement the notify method
     * @param string $eventClass      The event class used to create an event and send it to the event dispatcher
     *
     * @throws Exception
     * @return \M6Web\Component\Redis\Manager
     */
    public function setEventDispatcher($eventDispatcher, $eventClass)
    {
        if (!is_object($eventDispatcher) || !method_exists($eventDispatcher, 'dispatch')) {
            throw new Exception("The EventDispatcher must be an object and implement a dispatch method");
        }

        if (!class_exists($eventClass) ||
            !method_exists($eventClass, 'setCommand') ||
            !method_exists($eventClass, 'setArguments') ||
            !method_exists($eventClass, 'setExecutionTime')
            ) {
            $msg = "The Event class : ".$eventClass." must implement the setCommand, ";
            $msg .= "setExecutionTime and the setArguments method";
            throw new Exception($msg);
        }
        $this->eventDispatcher = $eventDispatcher;
        $this->eventClass      = $eventClass;

        return $this;
    }

    /**
     * init for the class
     * @param array $params confg array
     *
     * @throws Exception
     * @return \M6Web\Component\Redis\Manager
     * @throw Exception
     */
    protected function init($params)
    {
        // check serverConfig
        if (!isset($params['server_config'])) {
            throw new Exception("parameter serverConfig is mandatory");
        }

        ksort($params['server_config']);
        $this->setServerConfig($params['server_config']);
        if (isset($params['timeout'])) {
            $this->timeout = $params['timeout'];
        }
        if (isset($params['read_write_timeout'])) {
            $this->read_write_timeout = $params['read_write_timeout'];
        } else {
            // use the same timeout
            $this->read_write_timeout = $this->timeout;
        }
        if (isset($params['compress']) and is_bool($params['compress'])) {
            $this->compress = $params['compress'];
        }

        if (isset($params['reconnect']) and is_int($params['reconnect'])) {
            $this->reconnect = $params['reconnect'];
        }

        if (isset($params['event_name'])) {
            $this->eventName = (string) $params['event_name'];
        }

        return $this;
    }

    /**
     * retourn compress param
     * @return bool
     */
    public function getCompress()
    {
        return $this->compress;
    }

    /**
     * check the server config
     * @param array $servers host_config
     *
     * @return boolean
     */
    protected function checkServerConfig($servers)
    {
        if (!is_array($servers)) {
            return false;
        }
        foreach ($servers as $serverId => $server) {
            if (!is_string($serverId)) {
                return false;
            }
            if (!isset($server['ip']) or !isset($server['port'])) {
                return false;
            }
        }

        return true;
    }

    /**
     * getTimeout
     *
     * @return float
     */
    public function getTimeout()
    {
        return (float) $this->timeout;
    }

    /**
     * getReadWriteTimeout
     *
     * @return float
     */
    public function getReadWriteTimeout()
    {
        return (float) $this->read_write_timeout;
    }

    /**
     * get the server info against servernam or all the config
     * @param int $servername clé
     *
     * @return array
     */
    public function getServerConfig($servername = null)
    {
        if (is_null($servername)) {
            return $this->serverConfig;
        } else {
            return $this->serverConfig[$servername];
        }
    }

    /**
     * set the server config
     * @param array $servers config
     * @param bool  $check   do I have to check the config
     *
     * @throws Exception
     * @return \M6Web\Component\Redis\Manager
     */
    protected function setServerConfig($servers, $check = true)
    {
        if ($check and (!self::checkServerConfig($servers))) {
            throw new Exception("Le parametre serverConfig est mal formé");
        }
        // allow set only if the class var is null (one init only)
        if (is_null($this->serverConfig)) {
            $this->serverConfig = $servers;
        }

        return $this;
    }

    /**
     * return a server according to the redis key passed
     * @param string $key     server name
     * @param array  $servers array of servers
     *
     * @return mixed
     */
    protected function getServerId($key, $servers = null)
    {
        if (is_null($servers)) {
            $servers = $this->getServerConfig();
        }
        $serverKeys = array_keys($servers);

        return $serverKeys[(int) (crc32($key) % count($serverKeys))];
    }

    /**
     * return a Redis object according to the key
     * @param string $key     cache key
     * @param array  $servers servers
     *
     * @throws Exception
     * @return object
     */
    protected function getRedis($key, $servers = null)
    {
        if (is_null($servers)) {
            $servers = $this->getServerConfig(); // all the servers
        }
        if (0 == count($servers)) {
            throw new Exception("No redis server available ! ");
        }
        $idServer = $this->getServerId($key, $servers);

        if (!($redis = $this->getRedisFromServerConfig($idServer))) {
            $this->notifyEvent('redis_host_on_error', array($idServer));
            // find another server !!!
            unset($servers[$idServer]); // supress the server

            return $this->getRedis($key, $servers); // RECURSION ^^
        }
        $redis->select($this->getCurrentDb());

        return $redis;
    }

    /**
     * buid a redis server with a config
     * @param string $idServer server id in the configuration
     *
     * @return object|false
     */
    public function getRedisFromServerConfig($idServer)
    {
        // redis already marked dead
        if (array_key_exists($idServer, $this->deadRedis)) {
            return false;
        }
        // redis already marked alive
        if (array_key_exists($idServer, $this->aliveRedis)) {
            if (!$this->aliveRedis[$idServer]->isConnected()) {
                $this->deadRedis[$idServer] = 1;

                return false;
            }

            return $this->aliveRedis[$idServer];
        }

        $redis = $this->getNewRedis();
        if ($this->connectServer($redis, $this->getServerConfig($idServer))) {
            $this->aliveRedis[$idServer] = $redis;

            return $redis;
        } else {
            $this->deadRedis[$idServer] = 1;

            return false;
        }
    }

    /**
     * return a Predis object
     *
     * @throws Exception
     * @return PredisProxy
     */
    protected function getNewRedis()
    {
        $proxy = new PredisProxy();

        return $proxy->setMaxConnectionLostAllowed($this->reconnect);
    }

    /**
     * connecte un server
     * @param object|\Predis\Client $redis  \Predis\Client
     * @param array                 $server array('ip' => , 'port' => , 'timeout' =>)
     *
     * @return boolean
     */
    protected function connectServer(PredisProxy $redis, $server)
    {
        try {
            $redis->callRedisConstructor(array(
                'host' => $server['ip'],
                'port' => (int) $server['port'],
                'timeout' => $this->getTimeout(),
                'read_write_timeout' => $this->getReadWriteTimeout()
                ));
                // check if we are connected
            $redis->connect();

            return true;
        } catch (Predis\Connection\ConnectionException $e) {
            return false;
        }

    }

    /**
     * compress a string
     * @param string $data a data to compress
     *
     * @return string
     */
    protected static function compress($data)
    {
        return gzcompress($data);
    }

    /**
     * uncompress a string
     * @param string $data data to uncompress
     *
     * @return string
     */
    protected static function uncompress($data)
    {
        return gzuncompress($data);
    }

    /**
     * forget all server marker dead or alive
     *
     * @return $this
     */
    public function forgetDeadOrAliveRedis()
    {
        $this->deadRedis  = array();
        $this->aliveRedis = array();

        return $this;
    }
}


<?php
/**
 * implement Redis for a cache
 *
 * class dealing only with set and get and propose a build-in namespace feature
 * you can have multiple redis server (see manager)
 *
 * @author Olivier Mansour
 */
namespace M6Web\Component\Redis;

use Predis;

/**
 * class specialized for a cache usage
 * you cant access here to the predis base object
 */
class Cache extends Manager
{
    const CACHE = 0; // dbname will always be 0

    /**
     * namespace used to prefix keys
     * @var string
     */
    protected $namespace = null;

    /**
     * using the multi feature (see redis.io) we will store set and get since exec is called
     * when exec is called, set and get are dispatched between servers during multiple transactions
     * @var boolean
     */
    protected $multi = false;

    /**
     * list of closures executed in multi mode
     * @var array
     */
    protected $execList = array();

    /**
     * class constructor
     * @param array $params Manager parameters
     *
     * @throws Exception
     */
    public function __construct($params)
    {
        if (!isset($params['namespace'])) {
            throw new Exception("Le parametre namespace est obligatoire");
        }
        $this->setNamespace($params['namespace']);
        parent::__construct($params);
        $this->currentDb = self::CACHE;
    }

    /**
     * set the namespace
     * @param string $v namespace (sort of)
     */
    public function setNamespace($v)
    {
        $this->namespace =  str_replace(array('\\', '?', '*', '[', ']', ':'), '', (string) $v).':';
    }

    /**
     * get the namespace
     * @return string
     */
    public function getNamespace()
    {
        return $this->namespace;
    }

    /**
     * get a value from the cache
     * @param string $key clé
     *
     * @return mixed|null Null if no value available
     */
    public function get($key)
    {
        $redis = $this->getRedis($key);
        $start = microtime(true);
        $ret = $redis->get($this->getPatternKey().$key); // ajout du pattern
        $this->notifyEvent('get', array($this->getPatternKey().$key), microtime(true) - $start);
        if ($ret and $this->getCompress()) {
            return self::uncompress($ret);
        } else {
            return $ret;
        }
    }

    /**
     * delete a key
     *
     * @param array $keys array of keys
     *
     * @return integer
     */
    public function del($keys)
    {
        if (!is_array($keys)) {
            $keys = array($keys);
        }
        $funcs = array();

        // séparation en serveur
        foreach ($keys as $key) {
            $keyP = $this->getPatternKey().$key;
            $funcs[$key] = function ($redis) use ($keyP) {
                $start = microtime(true);
                $count = $redis->del($keyP);
                if ((is_int($count)) and ($count > 0)) {
                    $this->notifyEvent('del', array($keyP), microtime(true) - $start);
                }

                return $count;
            };
        }

        if (true === $this->multi) {
            //throw new Exception("not implemented yet");
            foreach ($funcs as $k => $func) {
                $this->addToExecList($k, $func);
            }

            return $this;
        } else {
            $ret = 0;
            foreach ($funcs as $k => $func) {
                $ret += $func($this->getRedis($k)); // execution
            }

            return $ret;
        }
    }

    /**
     * set a value
     *
     * @param string  $key   la clé
     * @param string  $value valeur
     * @param integer $ttl   time to live
     *
     * @return Cache
     */
    public function set($key, $value, $ttl = null)
    {
        if ($this->getCompress()) {
            $value = self::compress($value);
        }

        $keyP = $this->getPatternKey().$key;

        if (is_null($ttl)) {
            $func = function ($redis) use ($keyP, $value) {
                $start = microtime(true);
                $redis->set($keyP, $value);
                $this->notifyEvent('set', array($keyP, $value), microtime(true) - $start);

                return $redis;
            };
        } else {
            $func = function ($redis) use ($keyP, $value, $ttl) {
                $start = microtime(true);
                $redis->setex($keyP, $ttl, $value);
                $this->notifyEvent('setex', array($keyP, $ttl, $value), microtime(true) - $start);

                return $redis;
            };
        }

        if (true === $this->multi) {
            $this->addToExecList($key, $func);
        } else {
            $func($this->getRedis($key)); // set "normal"
        }

        return $this;
    }

    /**
     * check if a key exist
     *
     * @param string $key clé
     *
     * @return boolean
     */
    public function exists($key)
    {
        $start = microtime(true);
        $ret = $this->getRedis($key)->exists($this->getPatternKey().$key);
        $this->notifyEvent('exists', array($this->getPatternKey().$key), microtime(true) - $start);

        return (boolean) $ret;
    }

    /**
     * return the type of a key
     *
     * @param string $key clé
     *
     * @return mixed
     */
    public function type($key)
    {
        $start = microtime(true);
        $ret = (string)$this->getRedis($key)->type($this->getPatternKey().$key);
        $this->notifyEvent('type', array($this->getPatternKey().$key), microtime(true) - $start);

        return $ret;
    }

    /**
     * return a key ttl
     * @param string $key clé
     *
     * @return integer
     */
    public function ttl($key)
    {
        $start = microtime(true);
        $ret = $this->getRedis($key)->ttl($this->getPatternKey().$key);
        $this->notifyEvent('ttl', array($this->getPatternKey().$key), microtime(true) - $start);

        return $ret;
    }

    /**
     * increment a value (incr)
     *
     * @param string  $key  la clé
     * @param integer $incr valeur
     *
     * @throws Exception
     * @return int
     */
    public function incr($key, $incr = 1)
    {
        $keyP = $this->getPatternKey().$key;
        $func = function ($redis) use ($keyP, $incr) {
            try {
                $start = microtime(true);
                $ret = $redis->incrby($keyP, $incr);
                $this->notifyEvent('incrby', array($keyP, $incr), microtime(true) - $start);

                return $ret;
            } catch (Predis\ServerException $e) {
                return null;
            }
        };

        if (true === $this->multi) {
            $this->addToExecList($key, $func);

            return $this;
        } else {
            if (is_null($ret = $func($this->getRedis($key)))) {
                throw new Exception("Cant increment key ".$key.", not an integer ?");
            }

            return $ret;
        }
    }

    /**
     * set the key ttl
     *
     * @param string  $key la clé
     * @param integer $ttl ttl en seconde
     *
     * @throws Exception
     * @return int
     */
    public function expire($key, $ttl)
    {
        if ($ttl <= 0) {
            throw new Exception('ttl arg cant be negative');
        }

        $keyP = $this->getPatternKey().$key;
        $func = function ($redis) use ($keyP, $ttl) {
            try {
                $start = microtime(true);
                $ret = $redis->expire($keyP, $ttl);
                $this->notifyEvent('expire', array($keyP, $ttl), microtime(true) - $start);

                return $ret;
            } catch (Predis\ServerException $e) {
                return null;
            }
        };

        // gestion du multi
        if (true === $this->multi) {
            $this->addToExecList($key, $func);

            return $this;
        } else {
            $ret = $func($this->getRedis($key));

            return $ret;
        }
    }

    /**
     * used the to build the namespace added to the keys
     * @return string
     */
    public function getPatternKey()
    {
        return $this->namespace;
    }

    /**
     * use for debugging !
     *
     * flush all keys in the namespace
     * @return integer number of deleted keys
     */
    public function flush()
    {
        $pattern = $this->getPatternKey();
        $arrReturn = $this->runOnAllRedisServer(
            function ($redis) use ($pattern) {
                $wasDeleted = 0;
                $allKeys = $redis->keys($pattern.'*'); // all keys started with the namespace
                if (count($allKeys)) {
                    $wasDeleted = $redis->del($allKeys);
                }

                return array($wasDeleted);
            }
        );

        return array_sum($arrReturn);
    }

    /**
     * watch
     *
     * @param string $key clé
     *
     * @return void
     */
    public function watch($key)
    {
        $redis = $this->getRedis($key);
        $this->notifyEvent('watch', array($this->getPatternKey().$key));
        $redis->watch($this->getPatternKey().$key);
    }

    /**
     * unwatch (flush all params watched)
     *
     * @return string
     */
    public function unwatch()
    {
        return $this->runOnAllRedisServer(
            function ($redis) {
                $this->notifyEvent('unwatch', array());

                return $redis->unwatch();
            }
        );
    }

    /**
     * allow a transactionnal execution
     *
     * @return Cache
     */
    public function multi()
    {
        $this->multi = true;
        $this->execList = array(); // purge the execList

        return $this;
    }

    /**
     * execute multiple commands
     *
     * @throws Exception
     * @return array outputs of the commands
     */
    public function exec()
    {
        if (true === $this->multi) {
            $ret         = array();
            $this->multi = false;
            // exec
            foreach ($this->execList as $todos) {
                $redis = $todos[0]['redis']; // ^^ not so secure ?
                $this->notifyEvent('multi', array());
                $redis->multi();
                foreach ($todos as $todo) {
                    if ($todo['function'] instanceof \Closure) {
                        $ret[] = $todo['function']($redis);
                    } else {
                        throw new Exception("Ce n'est pas une Closure !");
                    }
                }
                $this->notifyEvent('exec', array());
                $redis->exec();
            }
            $this->execList = array(); // purge the execList

            return $ret;
        } else {
            throw new Exception("you have to call multi before exec");
        }
    }

    /**
     * cancel a transaction
     *
     * @return Cache
     */
    public function discard()
    {
        $this->multi    = false;
        $this->execList = array(); // purge the execList

        return $this;
    }

    /**
     * return an array of server with all the keys stored in the cache
     * use only for debug
     *
     * @return array
     */
    public function getAllKeys()
    {
        $pattern = $this->getPatternKey();

        return $this->runOnAllRedisServer(
            function ($redis) use ($pattern) {
                $this->notifyEvent('keys', array($pattern.'*'));

                return $redis->keys($pattern.'*'); // toutes les clés commençant par le pattern
            }
        );
    }

    /**
     * return an array of server with all the keys stored in the cache according to $pattern
     * use only for debug
     *
     * @param string $pattern the pattern - ie : raoul*
     *
     * @return array
     */
    public function keys($pattern)
    {
        $pattern = $this->getPatternKey().$pattern;

        return $this->runOnAllRedisServer(
            function ($redis) use ($pattern) {
                $this->notifyEvent('keys', array($pattern));

                return $redis->keys($pattern);
            }
        );
    }

    /**
     * apply a Closure on all the servers
     * @param \Closure $func anonymous function
     *
     * @return array array of results returned bye redis commands
     */
    protected function runOnAllRedisServer(\Closure $func)
    {
        $ret = array();
        // loop on all servers
        foreach (array_keys($this->getServerConfig()) as $serverId) {
            if ($redis = $this->getRedisFromServerConfig($serverId)) {
                $retFunc = $func($redis);
                if (is_array($retFunc)) {
                    $ret = array_merge($ret, $retFunc);
                }
            }
        }

        return $ret;
    }

    /**
     * return an array or server with the number of keys stored on each
     *
     * @param string $idServer Id du server
     *
     * @return array / integer
     */
    public function dbSize($idServer = null)
    {
        if (is_null($idServer)) {
            $servers = $this->getServerConfig();

            $dbsize = array();
            foreach (array_keys($servers) as $idServer) {
                $redis = $this->getRedisFromServerConfig($idServer);

                $dbsize[$idServer] = $redis->dbsize();
            }
        } else {
            $redis = $this->getRedisFromServerConfig($idServer);

            $dbsize = $redis->dbsize();
        }

        return $dbsize;
    }

    /**
     * return info of a server or all
     * @param string $idServer Id du server
     *
     * @return mixed
     */
    public function info($idServer = null)
    {
        if (is_null($idServer)) {
            $servers = $this->getServerConfig();

            $info = array();
            foreach (array_keys($servers) as $idServer) {
                $redis = $this->getRedisFromServerConfig($idServer);

                $info[$idServer] = $redis->info();
            }
        } else {
            $redis = $this->getRedisFromServerConfig($idServer);

            $info = $redis->info();
        }

        return $info;
    }

    /**
     * method not allowed in cache mode
     *
     * @param string $v de toute façon j'm'en fiche
     *
     * @deprecated
     * @return object|void
     * @throws Exception
     */
    public function setCurrentDb($v)
    {
        throw new Exception("forbidden method in cache mode :".__METHOD__);
    }

    /**
     * add a task to the execution list
     *
     * @param string   $key  clé
     * @param callable $func closure à ajouter
     *
     * @return void
     */
    protected function addToExecList($key, \Closure $func)
    {
        $redis = $this->getRedis($key);
        $this->execList[md5(serialize($redis))][] = array(
            'key'      => $key,
            'function' => $func,
            'redis'    => $redis
        );
    }
}


<?php
/**
 *
 * @author o_mansour
 */
namespace M6Web\Component\Redis;

/**
 * class for using Rédis as a DB
 */
class DB extends Manager
{
    /**
     * constructor - db is hardcoded
     * @param array $params Manager parameters
     *
     * @throws Exception
     */
    public function __construct($params)
    {
        $maxNbServer = 1;
        $this->setCurrentDb(1); // default hardcoded choice for the db
        if (count($params['server_config']) > $maxNbServer) {
            $msg = "cant declare more of ".$maxNbServer." servers, ".count($params['server_config'])." found ";
            throw new Exception($msg);
        }
        if (isset($params['compress']) and ($params['compress'] === true)) {
            throw new Exception("cant use the compress option in this class");
        }
        if (isset($params['namespace'])) {
            throw new Exception("cant use the namespace option in this class");
        }

        return parent::__construct($params);
    }

    /**
     *  return a predis object
     * @param int $serverRank
     *
     * @return false|object
     * @throws Exception
     */
    public function getRedisObject($serverRank = 0)
    {
        $serverConfig = $this->getServerConfig();

        $keys = array_keys($serverConfig);
        if (isset($keys[$serverRank])) {
            return $this->getRedisFromServerConfig($keys[$serverRank]);
        } else {
            throw new Exception("No server found at rank ".$serverRank);
        }
    }

    /**
     *  magic method to the \Redis() proxy
     *
     * @param string $name      method name
     * @param array  $arguments method arguments
     *
     * @throws Exception
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        if ($redis = $this->getRedisObject()) {
            $start = microtime(true);
            try {
                $ret = call_user_func_array(array($redis, $name), $arguments);
                $this->notifyEvent($name, $arguments, microtime(true) - $start);

                return $ret;
            } catch (\Predis\PredisException $e) {
                throw new Exception("Error calling the method ".$name." : ".$e->getMessage());
            }
        } else {
            throw new Exception("Cant connect to Redis");
        }
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\FileLocator;

/**
 * This is the class that loads and manages your bundle configuration
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
 */
class M6WebStatsdExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config        = $this->processConfiguration($configuration, $configs);
        $servers       = isset($config['servers']) ? $config['servers'] : [];
        $clients       = isset($config['clients']) ? $config['clients'] : [];

        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');

        $clientServiceNames = [];
        foreach ($clients as $alias => $clientConfig) {
            // load client in the container
            $clientServiceNames[] = $this->loadClient(
                $container,
                $alias,
                $clientConfig,
                $servers,
                $config['base_collectors']
            );
        }
        if ($container->getParameter('kernel.debug')) {
            $definition = new Definition('M6Web\Bundle\StatsdBundle\DataCollector\StatsdDataCollector');
            $definition->setPublic(true);
            $definition->addTag(
                'data_collector',
                [
                    'template' => '@M6WebStatsd/Collector/statsd.html.twig',
                    'id' => 'statsd'
                ]
            );

            $definition->addTag(
                'kernel.event_listener',
                [
                    'event' => 'kernel.response',
                    'method' => 'onKernelResponse'
                ]
            );

            foreach ($clientServiceNames as $serviceName) {
                $definition->addMethodCall('addStatsdClient', [$serviceName, new Reference($serviceName)]);
            }

            $container->setDefinition('m6.data_collector.statsd', $definition);
        }

        // Listner of console events
        if ($config['console_events']) {
            $container
                ->register(
                    'm6.listener.statsd.console',
                    'M6Web\Bundle\StatsdBundle\Listener\ConsoleListener'
                )
                ->addTag(
                    'kernel.event_listener',
                    ['event' => 'console.command', 'method' => 'onCommand']
                )
                ->addTag(
                    'kernel.event_listener',
                    ['event' => 'console.exception', 'method' => 'onException']
                )
                ->addTag(
                    'kernel.event_listener',
                    ['event' => 'console.terminate', 'method' => 'onTerminate']
                )
                ->addMethodCall('setEventDispatcher', [new Reference('event_dispatcher')]);
        }
    }

    /**
     * Load a client configuration as a service in the container. A client can use multiple servers
     *
     * @param ContainerInterface $container  The container
     * @param string             $alias      Alias of the client
     * @param array              $config     Base config of the client
     * @param array              $servers    List of available servers as describe in the config file
     * @param boolean            $baseEvents Register base events
     *
     * @throws InvalidConfigurationException
     * @return string the service name
     */
    protected function loadClient($container, $alias, array $config, array $servers, $baseEvents)
    {
        $usedServers    = [];
        $events         = $config['events'];
        $matchedServers = [];

        if ($config['servers'][0] == 'all') {
            // Use all servers
            $matchedServers = array_keys($servers);
        } else {
            // Use only declared servers
            foreach ($config['servers'] as $serverAlias) {

                // Named server
                if (array_key_exists($serverAlias, $servers)) {
                    $matchedServers[] = $serverAlias;
                    continue;
                }

                // Search matchning server config name
                $found = false;
                foreach (array_keys($servers) as $key) {
                    if (fnmatch($serverAlias, $key)) {
                        $matchedServers[] = $key;
                        $found            = true;
                    }
                }

                // No server found
                if (!$found) {
                    throw new InvalidConfigurationException(sprintf(
                        'M6WebStatsd client %s used server %s which is not defined in the servers section',
                        $alias,
                        $serverAlias
                    ));
                }
            }
        }

        // Matched server congurations
        foreach ($matchedServers as $serverAlias) {
            $usedServers[] = [
                'address' => $servers[$serverAlias]['address'],
                'port'    => $servers[$serverAlias]['port']
            ];
        }

        // Add the statsd client configured
        $serviceId  = ($alias == 'default') ? 'm6_statsd' : 'm6_statsd.'.$alias;
        $definition = new Definition('M6Web\Bundle\StatsdBundle\Client\Client');
        $definition->setPublic(true);
        $definition->addArgument($usedServers);

        if (isset($config['to_send_limit'])) {
            $definition->addMethodCall('setToSendLimit', [$config['to_send_limit']]);
        }

        foreach ($events as $eventName => $eventConfig) {
            $definition->addTag('kernel.event_listener', ['event' => $eventName, 'method' => 'handleEvent']);
            $definition->addMethodCall('addEventToListen', [$eventName, $eventConfig]);
        }

        $definition->addMethodCall('setPropertyAccessor', [new Reference('property_accessor_statsdbundle')]);

        $container->setDefinition($serviceId, $definition);

        // Add the statsd client listener
        $serviceListenerId = $serviceId.'.listener';
        $definition = new Definition('M6Web\Bundle\StatsdBundle\Statsd\Listener');
        $definition->setPublic(true);
        $definition->addArgument(new Reference($serviceId));
        $definition->addArgument(new Reference('event_dispatcher'));
        $definition->addTag('kernel.event_listener', [
            'event'    => 'kernel.terminate',
            'method'   => 'onKernelTerminate',
            'priority' => -100
        ]);
        $definition->addTag('kernel.event_listener', [
            'event'    => 'console.terminate',
            'method'   => 'onConsoleTerminate',
            'priority' => -100
        ]);

        if ($baseEvents) {
            $definition->addTag('kernel.event_listener', [
                'event' => 'kernel.terminate',
                'method' => 'dispatchBaseEvents',
                'priority' => 0
            ]);
            $definition->addTag('kernel.event_listener', [
                'event' => 'kernel.exception',
                'method' => 'onKernelException',
                'priority' => 0
            ]);
        }
        $container->setDefinition($serviceListenerId, $definition);

        return $serviceId;
    }

    /**
     * select an alias for the extension
     *
     * trick allowing bypassing the Bundle::getContainerExtension check on getAlias
     * not very clean, to investigate
     *
     * @return string
     */
    public function getAlias()
    {
        return 'm6_statsd';
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * This is the class that validates and merges configuration from your app/config files
 *
 * To learn more see
 * {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('m6_statsd');

        $this->addServersSection($rootNode);
        $this->addClientsSection($rootNode);
        $this->addDefaultEventSection($rootNode);

        return $treeBuilder;
    }

    private function addServersSection($rootNode)
    {
        $rootNode
            ->children()
                ->arrayNode('servers')
                    ->useAttributeAsKey('alias', false)
                    ->prototype('array')
                        ->children()
                            ->scalarNode('address')
                                ->isRequired()
                                ->validate()
                                    ->ifTrue(
                                        function ($v) {
                                            return substr($v, 0, 6) != 'udp://';
                                        }
                                    )
                                    ->thenInvalid("address parameter should begin with 'udp://'")
                                ->end()
                            ->end()
                            ->scalarNode('port')->isRequired()->end()
                        ->end()
                    ->end()
                ->end()
            ->end();
    }

    private function addClientsSection($rootNode)
    {
        $rootNode
            ->children()
                ->arrayNode('clients')
                    ->useAttributeAsKey('alias', false)
                    ->prototype('array')
                        ->children()
                            ->arrayNode('servers')
                                ->prototype('scalar')->end()
                            ->end()
                            ->arrayNode('events')
                                ->useAttributeAsKey('eventName')
                                ->prototype('array')
                                    ->children()
                                        ->scalarNode('increment')->end()
                                        ->scalarNode('decrement')->end()
                                        ->scalarNode('count')->end()
                                        ->scalarNode('gauge')->end()
                                        ->scalarNode('set')->end()
                                        ->scalarNode('timing')->end()
                                        ->arrayNode('custom_timing')
                                            ->children()
                                                ->scalarNode('node')->end()
                                                ->scalarNode('method')->end()
                                            ->end()
                                        ->end()
                                        ->arrayNode('tags')
                                            ->prototype('scalar')->end()
                                        ->end()
                                        ->booleanNode('immediate_send')->defaultFalse()->end()
                                    ->end()
                                ->end()
                            ->end()
                            ->integerNode('to_send_limit')->min(1)->end()
                        ->end()
                    ->end()
                ->end()
            ->end();
    }

    /**
     * addDefaultEventSection
     *
     * @param mixed $rootNode
     */
    private function addDefaultEventSection($rootNode)
    {
        $rootNode
            ->children()
                ->booleanNode('base_collectors')
                    ->defaultFalse()
                ->end()
                ->booleanNode('console_events')
                    ->defaultFalse()
                ->end();
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Form\Exception\UnexpectedTypeException;

/**
 * validate a graphite node
 * is the value suitable for graphite
 *
 * @Annotation
 */
class NodeValidator extends ConstraintValidator
{

    /**
     * {@inheritDoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
            $this->context->addViolation('node is not given or wrong datatype');

            return;
        }

        if (empty($value)) {
            $this->context->addViolation('the node is empty and isn\'t suitable for graphite');

            return;
        }

        if (!self::validatePattern($value)) {
            $this->context->addViolation('the node : '.$value.' isn\'t suitable for graphite');

            return;
        }
    }

    /**
     * Validate against the node patern
     *
     * @param string $value
     *
     * @return boolean
     */
    public static function validatePattern($value)
    {
        $pattern = "#^[a-z0-9\.]+$#i";

        return (boolean) preg_match($pattern, $value);
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\Listener;

use M6Web\Bundle\StatsdBundle\Event\ConsoleEvent;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Event\ConsoleEvent as BaseConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

/**
 * Listen to symfony command events
 * then trigger new custom events
 */
class ConsoleListener
{
    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher = null;

    /**
     * Time when command started
     *
     * @var float
     */
    protected $startTime = null;

    /**
     * Define event dispatch
     *
     * @param EventDispatcherInterface $ev
     */
    public function setEventDispatcher(EventDispatcherInterface $ev)
    {
        $this->eventDispatcher = $ev;
    }

    /**
     * @param BaseConsoleEvent $e
     */
    public function onCommand(BaseConsoleEvent $e)
    {
        $this->startTime = microtime(true);

        $this->dispatch(ConsoleEvent::COMMAND, $e);
    }

    /**
     * @param ConsoleTerminateEvent $e
     */
    public function onTerminate(ConsoleTerminateEvent $e)
    {
        // For non-0 exit command, fire an ERROR event
        if ($e->getExitCode() != 0) {
            $this->dispatch(ConsoleEvent::ERROR, $e);
        }

        $this->dispatch(ConsoleEvent::TERMINATE, $e);
    }

    /**
     * @param BaseConsoleEvent $e
     */
    public function onException(BaseConsoleEvent $e)
    {
        $this->dispatch(ConsoleEvent::EXCEPTION, $e);
    }

    /**
     * Dispatch custom event
     *
     * @param string           $eventName
     * @param BaseConsoleEvent $e
     *
     * @return boolean
     */
    protected function dispatch($eventName, BaseConsoleEvent $e)
    {
        if (!is_null($this->eventDispatcher)) {
            $class = str_replace(
                'Symfony\Component\Console\Event',
                'M6Web\Bundle\StatsdBundle\Event',
                get_class($e)
            );

            $finaleEvent = $class::createFromConsoleEvent(
                $e,
                $this->startTime,
                !is_null($this->startTime) ? microtime(true) - $this->startTime : null
            );

            return $this->eventDispatcher->dispatch($eventName, $finaleEvent);
        } else {
            return false;
        }
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\Client;

use M6Web\Bundle\StatsdBundle\Statsd\MonitorableEventInterface;
use M6Web\Component\Statsd\Client as BaseClient;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\PropertyAccess;

/**
 * Class that extends base statsd client, to handle auto-increment from event dispatcher notifications
 *
 */
class Client extends BaseClient
{
    protected $listenedEvents = [];

    /**
     * @var PropertyAccessInterface
     */
    protected $propertyAccessor;

    protected $toSendLimit;

    /**
     * getter for listenedEvents
     *
     * @return array
     */
    public function getListenedEvents()
    {
        return $this->listenedEvents;
    }

    /**
     * Add an event to listen
     *
     * @param string $eventName   The event name to listen
     * @param array  $eventConfig The event handler configuration
     */
    public function addEventToListen($eventName, $eventConfig)
    {
        $this->listenedEvents[$eventName] = $eventConfig;
    }

    /**
     * Set toSend limit
     *
     * @param int $toSendLimit
     *
     * @return $this
     */
    public function setToSendLimit($toSendLimit)
    {
        $this->toSendLimit = $toSendLimit;

        return $this;
    }

    /**
     * set the property accessor used in replaceConfigPlaceholder
     * @param PropertyAccess\PropertyAccessorInterface $propertyAccessor
     *
     * @return $this
     */
    public function setPropertyAccessor(PropertyAccess\PropertyAccessorInterface $propertyAccessor)
    {
        $this->propertyAccessor = $propertyAccessor;

        return $this;
    }

    /**
     * Handle an event
     *
     * @param EventInterface $event an event
     * @param string         $name  the event name
     *
     * @throws Exception
     * @return void
     */
    public function handleEvent($event, $name = null)
    {
        // this is used to stay compatible with Symfony 2.3
        if (is_null($name)) {
            $name = $event->getName();
        }

        if (!isset($this->listenedEvents[$name])) {
            return;
        }

        $config        = $this->listenedEvents[$name];
        $immediateSend = false;
        $tags          = $this->mergeTags($event, $config);

        // replace placeholders in tags
        $tags = array_map(function($tag) use ($event, $name) {
            return $this->replaceConfigPlaceholder($event, $name, $tag);
        }, $tags);

        foreach ($config as $conf => $confValue) {

            // increment
            if ('increment' === $conf) {
                $this->increment($this->replaceConfigPlaceholder($event, $name, $confValue), 1, $tags);
            } elseif ('decrement' === $conf) {
                $this->decrement($this->replaceConfigPlaceholder($event, $name, $confValue), 1, $tags);
            } elseif ('count' === $conf) {
                $value = $this->getEventValue($event, 'getValue');
                $this->count($this->replaceConfigPlaceholder($event, $name, $confValue), $value, 1, $tags);
            } elseif ('gauge' === $conf) {
                $value = $this->getEventValue($event, 'getValue');
                $this->gauge($this->replaceConfigPlaceholder($event, $name, $confValue), $value, 1, $tags);
            } elseif ('set' === $conf) {
                $value = $this->getEventValue($event, 'getValue');
                $this->set($this->replaceConfigPlaceholder($event, $name, $confValue), $value, 1, $tags);
            } elseif ('timing' === $conf) {
                $this->addTiming($event, 'getTiming', $this->replaceConfigPlaceholder($event, $name, $confValue), $tags);
            } elseif (('custom_timing' === $conf) and is_array($confValue)) {
                $this->addTiming($event, $confValue['method'], $this->replaceConfigPlaceholder($event, $name, $confValue['node']), $tags);
            } elseif ('immediate_send' === $conf) {
                $immediateSend = $confValue;
            } elseif ('tags' === $conf) {
                // nothing
            } else {
                throw new Exception("configuration : ".$conf." not handled by the StatsdBundle or its value is in a wrong format.");
            }
        }

        if (null !== $this->toSendLimit && $this->getToSend()->count() >= $this->toSendLimit) {
            $immediateSend = true;
        }

        if ($immediateSend) {
            $this->send();
        }
    }

    /**
     * getEventValue
     *
     * @param Event  $event
     * @param string $method
     *
     * @return mixed
     * @throws Exception
     */
    private function getEventValue($event, $method)
    {
        if (!method_exists($event, $method)) {
            throw new Exception("The event class ".get_class($event)." must have a ".$method." method in order to mesure value");
        }

        return call_user_func(array($event,$method));
    }

    /**
     * Factorisation of the timing method
     * find the value timed
     *
     * @param object $event Event
     * @param string $timingMethod Callable method in the event
     * @param string $node Node
     * @param array  $tags Tags key => value for influxDb
     *
     * @throws Exception
     */
    private function addTiming($event, $timingMethod, $node, $tags = [])
    {
        $timing = $this->getEventValue($event, $timingMethod);
        if ($timing > 0) {
            $this->timing($node, $timing, 1, $tags);
        }
    }

    /**
     * Replaces a string with a method name
     *
     * @param EventInterface $event An event
     * @param string         $eventName The name of the event
     * @param string         $string  The node in which the replacing will happen
     *
     * @return string
     */
    private function replaceConfigPlaceholder($event, $eventName, $string)
    {
        // `event->getName()` is deprecated, we have to replace <name> directly with $eventName
        $string = str_replace('<name>', $eventName, $string);

        if ((preg_match_all('/<([^>]*)>/', $string, $matches) > 0) and ($this->propertyAccessor !== null)) {
            $tokens = $matches[1];
            foreach ($tokens as $token) {
                $value = $this->propertyAccessor->getValue($event, $token);
                $string = str_replace('<'.$token.'>', $value, $string);
            }
        }

        return $string;
    }

    /**
     * Merge config tags with tags manually sent with the event
     *
     * @param mixed $event
     * @param array $config
     *
     * @return array of tags
     */
    private function mergeTags($event, $config)
    {
        $configTags = isset($config['tags']) ? $config['tags'] : [];

        if ($event instanceof MonitorableEventInterface) {
            return array_merge($configTags, $event->getTags());
        }

        return $configTags;
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\Statsd;

use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

use M6Web\Component\Statsd\Client;

/**
 * classe pour le service statsd
 */
class Listener
{
    protected $statsdClient;

    /**
     * Construct the listener, injecting the statsd client service
     *
     * @param Client                   $statsdClient    The statsd client service
     * @param EventDispatcherInterface $eventDispatcher Event dispatcher to use
     */
    public function __construct(Client $statsdClient, EventDispatcherInterface $eventDispatcher)
    {
        $this->statsdClient    = $statsdClient;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * onKernelException
     *
     * @param GetResponseForExceptionEvent $event
     */
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        $exception = $event->getException();
        if ($exception instanceof HttpExceptionInterface) {
            $code = $event->getException()->getStatusCode();
        } else {
            $code = 'unknown';
        }
        $this->eventDispatcher->dispatch(
            'statsd.exception',
            new StatsdEvent($code)
        );
    }

    /**
     * method called on the kernel.terminate event
     *
     * @param PostResponseEvent $event event
     *
     * @return void
     */
    public function onKernelTerminate(PostResponseEvent $event)
    {
        $this->statsdClient->send();
    }

    /**
     * method called on the console.terminate event
     *
     * @param ConsoleTerminateEvent $event event
     *
     * @return void
     */
    public function onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        $this->statsdClient->send();
    }

    /**
     * method called if base_collectors = true in config to dispatch base events
     * (you still have to catch them)
     *
     * @param PostResponseEvent $event
     */
    public function dispatchBaseEvents(PostResponseEvent $event)
    {
        $this->dispatchMemory();
        $this->dispatchRequestTime($event);
    }

    /**
     * dispatchMemory dispatch a memory event
     */
    private function dispatchMemory()
    {
        $memory = memory_get_peak_usage(true);
        $memory = ($memory > 1024 ? intval($memory / 1024) : 0);

        $this->eventDispatcher->dispatch(
            'statsd.memory_usage',
            new StatsdEvent($memory)
        );
    }

    /**
     * dispatchRequestTime dispatch the request time.
     * This time is a "fake" one, because some actions are performed before the initialization of the request
     * It is ~100ms smaller than the real kernel time.
     *
     * @param PostResponseEvent $event
     */
    private function dispatchRequestTime(PostResponseEvent $event)
    {
        $request   = $event->getRequest();
        $startTime = $request->server->get('REQUEST_TIME_FLOAT', $request->server->get('REQUEST_TIME'));
        $time      = microtime(true) - $startTime;
        $time      = round($time * 1000);

        $this->eventDispatcher->dispatch(
            'statsd.time',
            new StatsdEvent($time)
        );
    }
}


<?php
namespace M6Web\Bundle\StatsdBundle\Event;

use Symfony\Component\Console\Event\ConsoleEvent as BaseConsoleEvent;
use Symfony\Component\EventDispatcher\Event;

/**
 * Base console event
 */
abstract class ConsoleEvent extends Event
{
    const COMMAND   = 'm6web.console.command';
    const TERMINATE = 'm6web.console.terminate';
    const ERROR     = 'm6web.console.error';
    const EXCEPTION = 'm6web.console.exception';

    /**
     * Original triggered console event
     *
     * @var BaseConsoleEvent
     */
    protected $originalEvent;

    /**
     * Command start time
     *
     * @var float
     */
    protected $startTime;

    /**
     * Command execution time
     *
     * @var float
     */
    protected $executionTime;

    /**
     * @param BaseConsoleEvent $originalEvent
     * @param float            $startTime
     * @param float            $executionTime
     */
    public function __construct(BaseConsoleEvent $originalEvent, $startTime = null, $executionTime = null)
    {
        $this->originalEvent = $originalEvent;
        $this->startTime     = $startTime;
        $this->executionTime = $executionTime;
    }

    /**
     * Map calls to original event
     *
     * @param string $name
     * @param array  $parameters
     *
     * @return mixed
     */
    public function __call($name, $parameters)
    {
        return call_user_func_array(
            [$this->originalEvent, $name],
            $parameters
        );
    }

    /**
     * Get command start time
     *
     * @return float
     */
    public function getStartTime()
    {
        return $this->startTime;
    }

    /**
     * Get command execution time
     *
     * @return float Time ellapsed since command start (ms)
     */
    public function getExecutionTime()
    {
        return $this->executionTime * 1000;
    }

    /**
     * Alias of getExecutionTime
     * Allows timer simple usage
     *
     * @return float
     */
    public function getTiming()
    {
        return $this->getExecutionTime();
    }

    /**
     * Get peak memory usage
     *
     * @return int
     */
    public function getPeakMemory()
    {
        $memory = memory_get_peak_usage(true);
        $memory = ($memory > 1024 ? intval($memory / 1024) : 0);

        return $memory;
    }

    /**
     * @return BaseConsoleEvent
     */
    public function getOriginalEvent()
    {
        return $this->originalEvent;
    }

    /**
     * Get an underscored command name, if available
     *
     * @return string|null
     */
    public function getUnderscoredCommandName()
    {
        if (!is_null($command = $this->getCommand())) {
            return str_replace(':', '_', $command->getName());
        }

        return null;
    }

    /**
     * Create new event object
     *
     * @param BaseConsoleEvent $e
     * @param float            $startTime
     * @param float            $executionTime
     *
     * @return ConsoleEvent
     *
     * @throws \InvalidArgumentException
     */
    public static function createFromConsoleEvent(BaseConsoleEvent $e, $startTime = null, $executionTime = null)
    {
        if (static::support($e)) {
            return new static($e, $startTime, $executionTime);
        } else {
            throw \InvalidArgumentException('Invalid event type.');
        }
    }

    /**
     * Check if given event is supported by current class
     *
     * @param BaseConsoleEvent $e
     *
     * @return boolean
     */
    protected static function support(BaseConsoleEvent $e)
    {
        return true;
    }
}


<?php

namespace M6Web\Bundle\StatsdBundle\DataCollector;

use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;

/**
 * Handle datacollector for statsd
 */
class StatsdDataCollector extends DataCollector
{
    private $statsdClients;

    /**
     * Construct the data collector
     */
    public function __construct()
    {
        $this->reset();
    }

    /**
     * Reset the data collector to initial state
     */
    public function reset()
    {
        $this->statsdClients = [];
        $this->data = [
            'clients' => [],
            'operations' => 0,
        ];
    }

    /**
     * Kernel event
     *
     * @param EventInterface $event The received event
     */
    public function onKernelResponse($event)
    {
        if (HttpKernelInterface::MASTER_REQUEST == $event->getRequestType()) {
            foreach ($this->statsdClients as $clientName => $client) {
                $clientInfo = [
                    'name'       => $clientName,
                    'operations' => []
                ];
                foreach ($client->getToSend() as $operation) {
                    if ($operation) {
                        $this->data['operations']++;
                        $message = $operation['message'];

                        $clientInfo['operations'][] = [
                            'server' => $operation['server'],
                            'node'   => $message->getNode(),
                            'value'  => $message->getValue(),
                            'sample' => $message->getSampleRate(),
                            'unit'   => $message->getUnit()
                        ];
                    }
                }
                $this->data['clients'][] = $clientInfo;
            }
        }
    }

    /**
     * Add a statsd client to monitor
     *
     * @param string $clientAlias  The client alias
     * @param object $statsdClient A statsd client instance
     */
    public function addStatsdClient($clientAlias, $statsdClient)
    {
        $this->statsdClients[$clientAlias] = $statsdClient;
    }

    /**
     * Collect the data
     *
     * @param Request    $request   The request object
     * @param Response   $response  The response object
     * @param \Exception $exception An exception
     */
    public function collect(Request $request, Response $response, \Exception $exception = null)
    {
    }

    /**
     * Return the list of statsd operations
     *
     * @return array operations list
     */
    public function getClients()
    {
        return $this->data['clients'];
    }

    /**
     * Return the number of statsd operations
     *
     * @return integer the number of operations
     */
    public function getOperations()
    {
        return $this->data['operations'];
    }

    /**
     * Return the name of the collector
     *
     * @return string data collector name
     */
    public function getName()
    {
        return 'statsd';
    }
}


<?php
/**
 * Copyright (C) 2015 Derek J. Lambert
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

namespace CrEOF\Geo\WKT;

use Doctrine\Common\Lexer\AbstractLexer;

/**
 * Convert spatial value to tokens
 *
 * @author  Derek J. Lambert <dlambert@dereklambert.com>
 * @license http://dlambert.mit-license.org MIT
 */
class Lexer extends AbstractLexer
{
    const T_NONE               = 1;
    const T_INTEGER            = 2;
    const T_STRING             = 3;
    const T_FLOAT              = 5;
    const T_CLOSE_PARENTHESIS  = 6;
    const T_OPEN_PARENTHESIS   = 7;
    const T_COMMA              = 8;
    const T_DOT                = 10;
    const T_EQUALS             = 11;
    const T_MINUS              = 14;
    const T_SEMICOLON          = 50;
    const T_SRID               = 500;
    const T_ZM                 = 501;
    const T_Z                  = 502;
    const T_M                  = 503;

    // Geometry types > 600
    const T_TYPE                = 600;
    const T_POINT               = 601;
    const T_LINESTRING          = 602;
    const T_POLYGON             = 603;
    const T_MULTIPOINT          = 604;
    const T_MULTILINESTRING     = 605;
    const T_MULTIPOLYGON        = 606;
    const T_GEOMETRYCOLLECTION  = 607;
    const T_CIRCULARSTRING      = 608;
    const T_COMPOUNDCURVE       = 609;
    const T_CURVEPOLYGON        = 610;
    const T_MULTICURVE          = 611;
    const T_MULTISURFACE        = 612;
    const T_CURVE               = 613;
    const T_SURFACE             = 614;
    const T_POLYHEDRALSURFACE   = 615;
    const T_TIN                 = 616;
    const T_TRIANGLE            = 617;

    /**
     * @param string $input a query string
     */
    public function __construct($input = null)
    {
        if (null !== $input) {
            $this->setInput($input);
        }
    }

    /**
     * @return mixed
     */
    public function value()
    {
        return $this->token['value'];
    }

    /**
     * @param string $value
     *
     * @return int
     */
    protected function getType(&$value)
    {
        if (is_numeric($value)) {
            $value += 0;

            if (is_int($value)) {
                return self::T_INTEGER;
            }

            return self::T_FLOAT;
        }

        if (ctype_alpha($value)) {
            $name = __CLASS__ . '::T_' . strtoupper($value);

            if (defined($name)) {
                return constant($name);
            }

            return self::T_STRING;
        }

        switch ($value) {
            case ',':
                return self::T_COMMA;
            case '(':
                return self::T_OPEN_PARENTHESIS;
            case ')':
                return self::T_CLOSE_PARENTHESIS;
            case '=':
                return self::T_EQUALS;
            case ';':
                return self::T_SEMICOLON;
            default:
                return self::T_NONE;
        }
    }

    /**
     * @return string[]
     */
    protected function getCatchablePatterns()
    {
        return array(
            '',
            'zm|[a-z]+[a-ln-y]',
            '[+-]?[0-9]+(?:[\.][0-9]+)?(?:e[+-]?[0-9]+)?'
        );
    }

    /**
     * @return string[]
     */
    protected function getNonCatchablePatterns()
    {
        return array('\s+');
    }
}


<?php
/**
 * Copyright (C) 2015 Derek J. Lambert
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

namespace CrEOF\Geo\WKT;

use CrEOF\Geo\WKT\Exception\UnexpectedValueException;

/**
 * Parse WKT/EWKT spatial object strings
 *
 * @author  Derek J. Lambert <dlambert@dereklambert.com>
 * @license http://dlambert.mit-license.org MIT
 */
class Parser
{
    /**
     * @var string
     */
    private $type;

    /**
     * @var string
     */
    private $input;

    /**
     * @var int
     */
    private $srid;

    /**
     * @var string
     */
    private $dimension;

    /**
     * @var Lexer
     */
    private $lexer;

    /**
     * @param string|null $input
     */
    public function __construct($input = null)
    {
        $this->lexer = new Lexer();

        if (null !== $input) {
            $this->input = $input;
        }
    }

    /**
     * @param string|null $input
     *
     * @return array
     */
    public function parse($input = null)
    {
        if (null !== $input) {
            $this->input = $input;
        }

        $this->lexer->setInput($this->input);
        $this->lexer->moveNext();

        $this->srid      = null;
        $this->dimension = null;

        if ($this->lexer->isNextToken(Lexer::T_SRID)) {
            $this->srid = $this->srid();
        }

        $geometry              = $this->geometry();
        $geometry['srid']      = $this->srid;
        $geometry['dimension'] = '' === $this->dimension ? null : $this->dimension;

        return $geometry;
    }

    /**
     * Match SRID in EWKT object
     *
     * @return int
     */
    protected function srid()
    {
        $this->match(Lexer::T_SRID);
        $this->match(Lexer::T_EQUALS);
        $this->match(Lexer::T_INTEGER);

        $srid = $this->lexer->value();

        $this->match(Lexer::T_SEMICOLON);

        return $srid;
    }

    /**
     * Match spatial data type
     *
     * @return string
     */
    protected function type()
    {
        $this->match(Lexer::T_TYPE);

        return $this->lexer->value();
    }

    /**
     * Match spatial geometry object
     *
     * @return array
     */
    protected function geometry()
    {
        $type       = $this->type();
        $this->type = $type;

        if ($this->lexer->isNextTokenAny(array(Lexer::T_Z, Lexer::T_M, Lexer::T_ZM))) {
            $this->match($this->lexer->lookahead['type']);

            $this->dimension = $this->lexer->value();
        }

        $this->match(Lexer::T_OPEN_PARENTHESIS);

        $value = $this->$type();

        $this->match(Lexer::T_CLOSE_PARENTHESIS);

        return array(
            'type'  => $type,
            'value' => $value
        );
    }

    /**
     * Match a coordinate pair
     *
     * @return array
     */
    protected function point()
    {
        if (null !== $this->dimension) {
            return $this->coordinates(2 + strlen($this->dimension));
        }

        $values = $this->coordinates(2);

        for ($i = 3; $i <= 4 && $this->lexer->isNextTokenAny(array(Lexer::T_FLOAT, Lexer::T_INTEGER)); $i++) {
            $values[] = $this->coordinate();
        }

        switch (count($values)) {
            case 2:
                $this->dimension = '';
                break;
            case 3:
                $this->dimension = 'Z';
                break;
            case 4:
                $this->dimension = 'ZM';
                break;
        }

        return $values;
    }

    /**
     * @param int $count
     *
     * @return array
     */
    protected function coordinates($count)
    {
        $values = array();

        for ($i = 1; $i <= $count; $i++) {
            $values[] = $this->coordinate();
        }

        return $values;
    }

    /**
     * Match a number and optional exponent
     *
     * @return int|float
     */
    protected function coordinate()
    {
        $this->match(($this->lexer->isNextToken(Lexer::T_FLOAT) ? Lexer::T_FLOAT : Lexer::T_INTEGER));

        return $this->lexer->value();
    }

    /**
     * Match LINESTRING value
     *
     * @return array[]
     */
    protected function lineString()
    {
        return $this->pointList();
    }

    /**
     * Match POLYGON value
     *
     * @return array[]
     */
    protected function polygon()
    {
        return $this->pointLists();
    }

    /**
     * Match a list of coordinates
     *
     * @return array[]
     */
    protected function pointList()
    {
        $points = array($this->point());

        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);

            $points[] = $this->point();
        }

        return $points;
    }

    /**
     * Match nested lists of coordinates
     *
     * @return array[]
     */
    protected function pointLists()
    {
        $this->match(Lexer::T_OPEN_PARENTHESIS);

        $pointLists = array($this->pointList());

        $this->match(Lexer::T_CLOSE_PARENTHESIS);

        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
            $this->match(Lexer::T_OPEN_PARENTHESIS);

            $pointLists[] = $this->pointList();

            $this->match(Lexer::T_CLOSE_PARENTHESIS);
        }

        return $pointLists;
    }

    /**
     * Match MULTIPOLYGON value
     *
     * @return array[]
     */
    protected function multiPolygon()
    {
        $this->match(Lexer::T_OPEN_PARENTHESIS);

        $polygons = array($this->polygon());

        $this->match(Lexer::T_CLOSE_PARENTHESIS);

        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
            $this->match(Lexer::T_OPEN_PARENTHESIS);

            $polygons[] = $this->polygon();

            $this->match(Lexer::T_CLOSE_PARENTHESIS);
        }

        return $polygons;
    }

    /**
     * Match MULTIPOINT value
     *
     * @return array[]
     */
    protected function multiPoint()
    {
        return $this->pointList();
    }

    /**
     * Match MULTILINESTRING value
     *
     * @return array[]
     */
    protected function multiLineString()
    {
        return $this->pointLists();
    }

    /**
     * Match GEOMETRYCOLLECTION value
     *
     * @return array[]
     */
    protected function geometryCollection()
    {
        $collection = array($this->geometry());

        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);

            $collection[] = $this->geometry();
        }

        return $collection;
    }

    /**
     * Match token at current position in input
     *
     * @param $token
     */
    protected function match($token)
    {
        $lookaheadType = $this->lexer->lookahead['type'];

        if ($lookaheadType !== $token && ($token !== Lexer::T_TYPE || $lookaheadType <= Lexer::T_TYPE)) {
            throw $this->syntaxError($this->lexer->getLiteral($token));
        }

        $this->lexer->moveNext();
    }

    /**
     * Create exception with descriptive error message
     *
     * @param string $expected
     *
     * @return UnexpectedValueException
     */
    private function syntaxError($expected)
    {
        $expected = sprintf('Expected %s, got', $expected);
        $token    = $this->lexer->lookahead;
        $found    = null === $this->lexer->lookahead ? 'end of string.' : sprintf('"%s"', $token['value']);
        $message  = sprintf(
            '[Syntax Error] line 0, col %d: Error: %s %s in value "%s"',
            isset($token['position']) ? $token['position'] : '-1',
            $expected,
            $found,
            $this->input
        );

        return new UnexpectedValueException($message);
    }
}


<?php

namespace Zumba\GastonJS\NetworkTraffic;

/**
 * Class Request
 * @package Zumba\GastonJS\NetworkTraffic
 */
class Request {
  /** @var array */
  protected $data;
  /** @var array */
  protected $responseParts;

  /**
   * @param array $data
   * @param array $responseParts
   */
  public function __construct($data, $responseParts = null) {
    $this->data = $data;
    $this->responseParts = $this->createResponseParts($responseParts);
  }

  /**
   * Creates an array of Response objects from a given response array
   * @param $responseParts
   * @return array
   */
  protected function createResponseParts($responseParts) {
    if ($responseParts === null) {
      return array();
    }
    $responses = array();
    foreach ($responseParts as $responsePart) {
      $responses[] = new Response($responsePart);
    }
    return $responses;
  }

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

  /**
   * @param array $responseParts
   */
  public function setResponseParts($responseParts) {
    $this->responseParts = $responseParts;
  }

  /**
   * Returns the url where the request is going to be made
   * @return string
   */
  public function getUrl() {
    //TODO: add isset maybe?
    return $this->data['url'];
  }

  /**
   * Returns the request method
   * @return string
   */
  public function getMethod() {
    return $this->data['method'];
  }

  /**
   * Gets the request headers
   * @return array
   */
  public function getHeaders() {
    //TODO: Check if the data is actually an array, else make it array and see implications
    return $this->data['headers'];
  }

  /**
   * Returns if exists the request time
   * @return \DateTime
   */
  public function getTime() {
    if (isset($this->data['time'])) {
      $requestTime = new \DateTime();
      //TODO: fix the microseconds to miliseconds
      $requestTime->createFromFormat("Y-m-dTH:i:s.uZ", $this->data["time"]);
      return $requestTime;
    }
    return null;
  }

}


<?php

namespace Zumba\GastonJS\Browser;

use Zumba\GastonJS\Exception\BrowserError;
use Zumba\GastonJS\Exception\DeadClient;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;

/**
 * Class BrowserBase
 * @package Zumba\GastonJS\Browser
 */
class BrowserBase {
  /** @var mixed */
  protected $logger;
  /** @var  bool */
  protected $debug;
  /** @var  string */
  protected $phantomJSHost;
  /** @var  Client */
  protected $apiClient;

  /**
   *  Creates an http client to consume the phantomjs API
   */
  protected function createApiClient() {
    // Provide a BC switch between guzzle 5 and guzzle 6.
    if (class_exists('GuzzleHttp\Psr7\Response')) {
      $this->apiClient = new Client(array("base_uri" => $this->getPhantomJSHost()));
    }
    else {
      $this->apiClient = new Client(array("base_url" => $this->getPhantomJSHost()));
    }
  }

  /**
   * TODO: not sure how to do the normalizeKeys stuff fix when needed
   * @param $keys
   * @return mixed
   */
  protected function normalizeKeys($keys) {
    return $keys;
  }

  /**
   * @return Client
   */
  public function getApiClient() {
    return $this->apiClient;
  }

  /**
   * @return string
   */
  public function getPhantomJSHost() {
    return $this->phantomJSHost;
  }

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

  /**
   * Restarts the browser
   */
  public function restart() {
    //TODO: Do we really need to do this?, we are just a client
  }

  /**
   * Sends a command to the browser
   * @throws BrowserError
   * @throws \Exception
   * @return mixed
   */
  public function command() {
    try {
      $args = func_get_args();
      $commandName = $args[0];
      array_shift($args);
      $messageToSend = json_encode(array('name' => $commandName, 'args' => $args));
      /** @var $commandResponse \GuzzleHttp\Psr7\Response|\GuzzleHttp\Message\Response */
      $commandResponse = $this->getApiClient()->post("/api", array("body" => $messageToSend));
      $jsonResponse = json_decode($commandResponse->getBody(), TRUE);
    } catch (ServerException $e) {
      $jsonResponse = json_decode($e->getResponse()->getBody()->getContents(), true);
    } catch (ConnectException $e) {
      throw new DeadClient($e->getMessage(), $e->getCode(), $e);
    } catch (\Exception $e) {
      throw $e;
    }

    if (isset($jsonResponse['error'])) {
      throw $this->getErrorClass($jsonResponse);
    }

    return $jsonResponse['response'];
  }

  /**
   * @param $error
   * @return BrowserError
   */
  protected function getErrorClass($error) {
    $errorClassMap = array(
      'Poltergeist.JavascriptError'   => "Zumba\\GastonJS\\Exception\\JavascriptError",
      'Poltergeist.FrameNotFound'     => "Zumba\\GastonJS\\Exception\\FrameNotFound",
      'Poltergeist.InvalidSelector'   => "Zumba\\GastonJS\\Exception\\InvalidSelector",
      'Poltergeist.StatusFailError'   => "Zumba\\GastonJS\\Exception\\StatusFailError",
      'Poltergeist.NoSuchWindowError' => "Zumba\\GastonJS\\Exception\\NoSuchWindowError",
      'Poltergeist.ObsoleteNode'      => "Zumba\\GastonJS\\Exception\\ObsoleteNode"
    );
    if (isset($error['error']['name']) && isset($errorClassMap[$error["error"]["name"]])) {
      return new $errorClassMap[$error["error"]["name"]]($error);
    }

    return new BrowserError($error);
  }
}


<?php

namespace Zumba\GastonJS\Browser;

/**
 * Class Browser
 * @package Zumba\GastonJS
 */
class Browser extends BrowserBase {

  use BrowserAuthenticationTrait;
  use BrowserConfigurationTrait;
  use BrowserCookieTrait;
  use BrowserFileTrait;
  use BrowserFrameTrait;
  use BrowserHeadersTrait;
  use BrowserMouseEventTrait;
  use BrowserNavigateTrait;
  use BrowserNetworkTrait;
  use BrowserPageElementTrait;
  use BrowserPageTrait;
  use BrowserRenderTrait;
  use BrowserScriptTrait;
  use BrowserWindowTrait;

  /**
   * @param string $phantomJSHost
   * @param mixed  $logger
   */
  public function __construct($phantomJSHost, $logger = null) {
    $this->phantomJSHost = $phantomJSHost;
    $this->logger = $logger;
    $this->debug = false;
    $this->createApiClient();
  }

  /**
   * Returns the value of a given element in a page
   * @param $pageId
   * @param $elementId
   * @return mixed
   */
  public function value($pageId, $elementId) {
    return $this->command('value', $pageId, $elementId);
  }

  /**
   * Sets a value to a given element in a given page
   * @param $pageId
   * @param $elementId
   * @param $value
   * @return mixed
   */
  public function set($pageId, $elementId, $value) {
    return $this->command('set', $pageId, $elementId, $value);
  }

  /**
   * Tells whether an element on a page is visible or not
   * @param $pageId
   * @param $elementId
   * @return bool
   */
  public function isVisible($pageId, $elementId) {
    return $this->command('visible', $pageId, $elementId);
  }

  /**
   * @param $pageId
   * @param $elementId
   * @return bool
   */
  public function isDisabled($pageId, $elementId) {
    return $this->command('disabled', $pageId, $elementId);
  }

  /**
   * Drag an element to a another in a given page
   * @param $pageId
   * @param $fromId
   * @param $toId
   * @return mixed
   */
  public function drag($pageId, $fromId, $toId) {
    return $this->command('drag', $pageId, $fromId, $toId);
  }

  /**
   * Selects a value in the given element and page
   * @param $pageId
   * @param $elementId
   * @param $value
   * @return mixed
   */
  public function select($pageId, $elementId, $value) {
    return $this->command('select', $pageId, $elementId, $value);
  }

  /**
   * Triggers an event to a given element on the given page
   * @param $pageId
   * @param $elementId
   * @param $event
   * @return mixed
   */
  public function trigger($pageId, $elementId, $event) {
    return $this->command('trigger', $pageId, $elementId, $event);
  }

  /**
   * TODO: not sure what this does, needs to do normalizeKeys
   * @param int   $pageId
   * @param int   $elementId
   * @param array $keys
   * @return mixed
   */
  public function sendKeys($pageId, $elementId, $keys) {
    return $this->command('send_keys', $pageId, $elementId, $this->normalizeKeys($keys));
  }
}


<?php
namespace Zumba\GastonJS\NetworkTraffic;

/**
 * Class Response
 * @package Zumba\GastonJS\NetworkTraffic
 */
class Response {
  /** @var  array */
  protected $data;

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

  /**
   * Gets Response url
   * @return string
   */
  public function getUrl() {
    return $this->data['url'];
  }

  /**
   * Gets the response status code
   * @return int
   */
  public function getStatus() {
    return intval($this->data['status']);
  }

  /**
   * Gets the status text of the response
   * @return string
   */
  public function getStatusText() {
    return $this->data['statusText'];
  }

  /**
   * Gets the response headers
   * @return array
   */
  public function getHeaders() {
    return $this->data['headers'];
  }

  /**
   * Get redirect url if response is a redirect
   * @return string
   */
  public function getRedirectUrl() {
    if (isset($this->data['redirectUrl']) && !empty($this->data['redirectUrl'])) {
      return $this->data['redirectUrl'];
    }
    return null;
  }

  /**
   * Returns the size of the response body
   * @return int
   */
  public function getBodySize() {
    if (isset($this->data['bodySize'])) {
      return intval($this->data['bodySize']);
    }
    return 0;
  }

  /**
   * Returns the content type of the response
   * @return string
   */
  public function getContentType() {
    if (isset($this->data['contentType'])) {
      return $this->data['contentType'];
    }
    return null;
  }

  /**
   * Returns if exists the response time
   * @return \DateTime
   */
  public function getTime() {
    if (isset($this->data['time'])) {
      $requestTime = new \DateTime();
      //TODO: fix the microseconds to miliseconds
      $requestTime->createFromFormat("Y-m-dTH:i:s.uZ", $this->data["time"]);
      return $requestTime;
    }
    return null;
  }
}


<?php

namespace Zumba\GastonJS\Browser;

/**
 * Trait BrowserRenderTrait
 * @package Zumba\GastonJS\Browser
 */
trait BrowserRenderTrait {
  /**
   * Check and fix render options
   * @param $options
   * @return mixed
   */
  protected function checkRenderOptions($options) {
    //Default is full and no selection
    if (count($options) === 0) {
      $options["full"] = true;
      $options["selector"] = null;
    }

    if (isset($options["full"]) && isset($options["selector"])) {
      if ($options["full"]) {
        //Whatever it is, full is more powerful than selection
        $options["selector"] = null;
      }
    } else {
      if (!isset($options["full"]) && isset($options["selector"])) {
        $options["full"] = false;
      }
    }
    return $options;
  }

  /**
   * Renders a page or selection to a file given by path
   * @param string $path
   * @param array  $options
   * @return mixed
   */
  public function render($path, $options = array()) {
    $fixedOptions = $this->checkRenderOptions($options);
    return $this->command('render', $path, $fixedOptions["full"], $fixedOptions["selector"]);
  }

  /**
   * Renders base64 a page or selection to a file given by path
   * @param string $imageFormat (PNG, GIF, JPEG)
   * @param array  $options
   * @return mixed
   */
  public function renderBase64($imageFormat, $options = array()) {
    $fixedOptions = $this->checkRenderOptions($options);
    return $this->command('render_base64', $imageFormat, $fixedOptions["full"], $fixedOptions["selector"]);
  }

  /**
   * Sets the paper size, useful when saving to PDF
   * @param $paperSize
   * @return mixed
   */
  public function setPaperSize($paperSize) {
    return $this->command('set_paper_size', $paperSize);
  }
}