<?php

namespace Neutrino\Database\Migrations;

use InvalidArgumentException;
use Neutrino\Database\Migrations\Prefix\PrefixInterface;
use Neutrino\Support\Str;

/**
 * Class MigrationCreator
 *
 * @package Neutrino\Database\Migrations
 */
class MigrationCreator
{
    /**
     * @var \Neutrino\Database\Migrations\Prefix\PrefixInterface
     */
    protected $prefix;

    /**
     * MigrationCreator constructor.
     *
     * @param $prefix
     */
    public function __construct(PrefixInterface $prefix)
    {
        $this->prefix = $prefix;
    }

    /**
     * Create a new migration at the given path.
     *
     * @param  string $name
     * @param  string $path
     * @param  string $table
     * @param  bool   $create
     *
     * @return string
     * @throws \Exception
     */
    public function create($name, $path, $table = null, $create = false)
    {
        $this->ensureMigrationDoesntAlreadyExist($name, $path);

        $stub = $this->getStubContent($table, $create);

        $populatedStub = $this->populateStub($name, $stub, $table);

        $path = $this->getPath($name, $path);

        file_put_contents($path, $populatedStub);

        return $path;
    }

    /**
     * Ensure that a migration with the given name doesn't already exist.
     *
     * @param  string $name
     * @param  string $path
     *
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    protected function ensureMigrationDoesntAlreadyExist($name, $path)
    {
        if (class_exists($className = $this->getClassName($name))) {
            throw new InvalidArgumentException("A {$className} class already exists.");
        }

        // TODO Review check for version 2.0
        if(!empty($files = glob($path . '/*_*.php'))){
            foreach ($files as $file) {
                $file = str_replace('.php', '', basename($file));

                $migration = $this->prefix->deletePrefix($file);

                if($className === $this->getClassName($migration)){
                    throw new InvalidArgumentException("A {$className} class already exists.");
                }
            }
        }
    }

    /**
     * Get the migration stub file.
     *
     * @param  string $table
     * @param  bool   $create
     *
     * @return string
     */
    protected function getStubContent($table, $create)
    {
        if (is_null($table)) {
            $file = $this->stubsPath() . '/blank.stub';
        } else {
            $stub = $create ? 'create.stub' : 'update.stub';

            $file = $this->stubsPath() . '/' . $stub;
        }

        return file_get_contents($file);
    }

    /**
     * Populate the place-holders in the migration stub.
     *
     * @param  string $name
     * @param  string $stub
     * @param  string $table
     *
     * @return string
     */
    protected function populateStub($name, $stub, $table)
    {
        $stub = str_replace('{class}', $this->getClassName($name), $stub);

        if (!is_null($table)) {
            $stub = str_replace('{table}', $table, $stub);
        }

        return $stub;
    }

    /**
     * Get the class name of a migration name.
     *
     * @param  string $name
     *
     * @return string
     */
    protected function getClassName($name)
    {
        return Str::studly($name);
    }

    /**
     * Get the full path to the migration.
     *
     * @param  string $name
     * @param  string $path
     *
     * @return string
     */
    protected function getPath($name, $path)
    {
        if (!is_null($prefix = $this->prefix->getPrefix())) {
            $prefix .= '_';
        }

        return $path . '/' . $prefix . $name . '.php';
    }

    /**
     * Get the path to the stubs.
     *
     * @return string
     */
    public function stubsPath()
    {
        return __DIR__ . '/stubs';
    }
}


<?php namespace Igniter\Flame\ActivityLog\Models;

use Igniter\Flame\Database\Builder;
use Model;

/**
 * Activity Model Class
 *
 * @package        Igniter\Flame\ActivityLog\Models
 */
class Activity extends Model
{
    /**
     * @var array Auto-fill the created date field on insert
     */
    const CREATED_AT = 'date_added';

    const UPDATED_AT = 'date_updated';

    /**
     * @var string The database table name
     */
    public $table = 'activities';

    /**
     * @var string The database table primary key
     */
    public $primaryKey = 'activity_id';

    public $timestamps = TRUE;

    public $casts = [
        'properties' => 'collection',
    ];

    public $relation = [
        'morphTo' => [
            'subject' => [],
            'causer'  => [],
        ],
    ];

    //
    // Scopes
    //

    /**
     * Scope a query to only include activities by a given causer.
     *
     * @param \Igniter\Flame\Database\Builder $query
     * @param Model $causer
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeCausedBy(Builder $query, Model $causer)
    {
        return $query
            ->where('causer_type', $causer->getMorphClass())
            ->where('causer_id', $causer->getKey());
    }

    /**
     * Scope a query to only include activities for a given subject.
     *
     * @param \Igniter\Flame\Database\Builder $query
     * @param Model $subject
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeForSubject(Builder $query, Model $subject)
    {
        return $query
            ->where('subject_type', $subject->getMorphClass())
            ->where('subject_id', $subject->getKey());
    }
}


<?php

namespace Neutrino\Foundation\Cli\Tasks;

use Neutrino\Assets\SassCompiler;
use Neutrino\Cli\Task;

/**
 * Class AssetsTask
 *
 * Neutrino\Foundation\Cli\Tasks
 */
class AssetsSassTask extends Task
{
    /**
     * Compilation des assets sass.
     * Run command :
     *    sass {src} {dest} [--style={outputStyle}] [--sourcemap={type}]
     *
     * @option --src={path}          : Define the src file. If not specified, we compile all files in config
     * @option --dest={path}         : Define the dest file. If not specified, we compile all files in config
     * @option --sourcemap={type}    : Define sourcemap type
     * @option --style={outputStyle} : Define output style
     * @option --compress            : alias of --style=compressed
     * @option --nested              : alias of --style=nested
     * @option --compact             : alias of --style=compact
     * @option --expanded            : alias of --style=expanded
     *
     * @throws \Exception
     */
    public function mainAction()
    {
        if($this->hasOption('src') && $this->hasOption('dest')){
            $src = $this->getOption('src');
            $dest = $this->getOption('dest');

            $this->compile($src, $dest);
            return;
        }

        if($this->hasOption('src')){
            $this->block(['You pass {src} option, without {dest} option.'], 'error');
            return;
        }

        if($this->hasOption('dest')){
            $this->block(['You pass {dest} option, without {src} option.'], 'error');
            return;
        }

        $files = $this->config->assets->sass->files;

        foreach ($files as $src => $dest) {
            $this->compile($src, $dest);
        }
    }

    private function compile($src, $dest)
    {
        $this->notice('Compiling : ');
        $this->notice("\tsrc  : $src");
        $this->notice("\tdest : $dest");

        try {
            $this->getDI()->get(SassCompiler::class)->compile([
                'sass_file'   => $src,
                'output_file' => $dest,
                'cmd_options' => array_filter([
                    ($style = $this->getSassStyle()) ? "--style=$style" : '',
                    ($sourcemap = $this->getSourcemap()) ? '--sourcemap="' . $sourcemap . '"' : '',
                ])
            ]);
        } catch (\Exception $e) {
            $this->block([$e->getMessage()], 'error');
            return;
        }

        $this->info('Success !');
    }

    private function getSassStyle()
    {
        if ($this->hasOption('compress')) {
            return 'compressed';
        }
        if ($this->hasOption('nested')) {
            return 'nested';
        }
        if ($this->hasOption('compact')) {
            return 'compact';
        }
        if ($this->hasOption('expanded')) {
            return 'expanded';
        }

        switch ($compress = $this->getOption('output')) {
            case 'compressed':
            case 'nested':
            case 'compact':
            case 'expanded':
            return $compress;
        }

        return isset($this->config->assets->sass->options->style)
            ? $this->config->assets->sass->options->style
            : null;
    }

    private function getSourcemap()
    {
        if ($this->hasOption('sourcemap')) {
            return $this->getOption('sourcemap');
        }
        if (isset($this->config->assets->sass->options->sourcemap)) {
            return $this->config->assets->sass->options->sourcemap;
        }

        return null;
    }
}


<?php namespace Igniter\Flame\Support;

use Exception;

/**
 * Configuration rewriter
 *
 * https://github.com/daftspunk/laravel-config-writer
 *
 * This class lets you rewrite array values inside a basic configuration file
 * that returns a single array definition (a Laravel config file) whilst maintaining
 * the integrity of the file, leaving comments and advanced settings intact.
 *
 * The following value types are supported for writing:
 * - strings
 * - integers
 * - booleans
 * - nulls
 * - single-dimension arrays
 *
 * To do:
 * - When an entry does not exist, provide a way to create it
 *
 * Pro Regex tip: Use [\s\S] instead of . for multi-line support
 */
class ConfigRewrite
{
    public function toFile($filePath, $newValues, $useValidation = TRUE)
    {
        $contents = file_get_contents($filePath);
        $contents = $this->toContent($contents, $newValues, $useValidation);
        file_put_contents($filePath, $contents);
        return $contents;
    }

    public function toContent($contents, $newValues, $useValidation = TRUE)
    {
        $contents = $this->parseContent($contents, $newValues);
        if (!$useValidation) {
            return $contents;
        }

        $result = eval('?>'.$contents);
        foreach ($newValues as $key => $expectedValue) {
            $parts = explode('.', $key);
            $array = $result;
            foreach ($parts as $part) {
                if (!is_array($array) || !array_key_exists($part, $array)) {
                    throw new Exception(sprintf('Unable to rewrite key "%s" in config, does it exist?', $key));
                }
                $array = $array[$part];
            }
            $actualValue = $array;
            if ($actualValue != $expectedValue) {
                throw new Exception(sprintf('Unable to rewrite key "%s" in config, rewrite failed', $key));
            }
        }

        return $contents;
    }

    protected function parseContent($contents, $newValues)
    {
        $result = $contents;
        foreach ($newValues as $path => $value) {
            $result = $this->parseContentValue($result, $path, $value);
        }

        return $result;
    }

    protected function parseContentValue($contents, $path, $value)
    {
        $result = $contents;
        $items = explode('.', $path);
        $key = array_pop($items);

        $replaceValue = $this->writeValueToPhp($value);

        $count = 0;
        $patterns = [];
        $patterns[] = $this->buildStringExpression($key, $items);
        $patterns[] = $this->buildStringExpression($key, $items, '"');
        $patterns[] = $this->buildConstantExpression($key, $items);
        $patterns[] = $this->buildArrayExpression($key, $items);
        foreach ($patterns as $pattern) {
            $result = preg_replace($pattern, '${1}${2}'.$replaceValue, $result, 1, $count);
            if ($count > 0) {
                break;
            }
        }

        return $result;
    }

    protected function writeValueToPhp($value)
    {
        if (is_string($value) && strpos($value, "'") === FALSE) {
            $replaceValue = "'".$value."'";
        }
        elseif (is_string($value) && strpos($value, '"') === FALSE) {
            $replaceValue = '"'.$value.'"';
        }
        elseif (is_bool($value)) {
            $replaceValue = ($value ? 'true' : 'false');
        }
        elseif (is_null($value)) {
            $replaceValue = 'null';
        }
        elseif (is_array($value) && count($value) === count($value, COUNT_RECURSIVE)) {
            $replaceValue = $this->writeArrayToPhp($value);
        }
        else {
            $replaceValue = $value;
        }

        $replaceValue = str_replace('$', '\$', $replaceValue);

        return $replaceValue;
    }

    protected function writeArrayToPhp($array)
    {
        $result = [];
        foreach ($array as $value) {
            if (!is_array($value)) {
                $result[] = $this->writeValueToPhp($value);
            }
        }

        return '['.implode(', ', $result).']';
    }

    protected function buildStringExpression($targetKey, $arrayItems = [], $quoteChar = "'")
    {
        $expression = [];

        // Opening expression for array items ($1)
        $expression[] = $this->buildArrayOpeningExpression($arrayItems);

        // The target key opening
        $expression[] = '([\'|"]'.$targetKey.'[\'|"]\s*=>\s*)['.$quoteChar.']';

        // The target value to be replaced ($2)
        $expression[] = '([^'.$quoteChar.']*)';

        // The target key closure
        $expression[] = '['.$quoteChar.']';

        return '/'.implode('', $expression).'/';
    }

    /**
     * Common constants only (true, false, null, integers)
     * @param $targetKey
     * @param array $arrayItems
     * @return string
     */
    protected function buildConstantExpression($targetKey, $arrayItems = [])
    {
        $expression = [];

        // Opening expression for array items ($1)
        $expression[] = $this->buildArrayOpeningExpression($arrayItems);

        // The target key opening ($2)
        $expression[] = '([\'|"]'.$targetKey.'[\'|"]\s*=>\s*)';

        // The target value to be replaced ($3)
        $expression[] = '([tT][rR][uU][eE]|[fF][aA][lL][sS][eE]|[nN][uU][lL]{2}|[\d]+)';

        return '/'.implode('', $expression).'/';
    }

    /**
     * Single level arrays only
     * @param $targetKey
     * @param array $arrayItems
     * @return string
     */
    protected function buildArrayExpression($targetKey, $arrayItems = [])
    {
        $expression = [];

        // Opening expression for array items ($1)
        $expression[] = $this->buildArrayOpeningExpression($arrayItems);

        // The target key opening ($2)
        $expression[] = '([\'|"]'.$targetKey.'[\'|"]\s*=>\s*)';

        // The target value to be replaced ($3)
        $expression[] = '(?:[aA][rR]{2}[aA][yY]\(|[\[])([^\]|)]*)[\]|)]';

        return '/'.implode('', $expression).'/';
    }

    protected function buildArrayOpeningExpression($arrayItems)
    {
        if (count($arrayItems)) {
            $itemOpen = [];
            foreach ($arrayItems as $item) {
                // The left hand array assignment
                $itemOpen[] = '[\'|"]'.$item.'[\'|"]\s*=>\s*(?:[aA][rR]{2}[aA][yY]\(|[\[])';
            }
            // Capture all opening array (non greedy)
            $result = '('.implode('[\s\S]*', $itemOpen).'[\s\S]*?)';
        }
        else {
            // Gotta capture something for $1
            $result = '()';
        }
        return $result;
    }
}


<?php

namespace Neutrino\Database\Cli\Tasks;

use Neutrino\Cli\Task;
use Neutrino\Constants\Env;
use Neutrino\Database\Migrations\MigrationCreator;
use Neutrino\Database\Migrations\Migrator;
use Neutrino\Database\Migrations\Storage\StorageInterface;

/**
 * Class BaseTask
 *
 * @package Neutrino\Database\Cli\Tasks
 */
abstract class BaseTask extends Task
{
    /**
     * The migrator instance.
     *
     * @var \Neutrino\Database\Migrations\Migrator
     */
    protected $migrator;

    /**
     * The Storage instance
     *
     * @var \Neutrino\Database\Migrations\Storage\StorageInterface
     */
    protected $storage;

    /**
     * The migration creator instance.
     *
     * @var \Neutrino\Database\Migrations\MigrationCreator
     */
    protected $creator;

    protected function onConstruct()
    {
        parent::onConstruct();

        $this->migrator = $this->getDI()->get(Migrator::class);
        $this->storage = $this->getDI()->get(StorageInterface::class);
        $this->creator = $this->getDI()->get(MigrationCreator::class);
    }

    /**
     * @return bool
     */
    protected function confirmToProceed()
    {
        if (APP_ENV === Env::PRODUCTION) {
            $this->warn("You will run migration on production environnement");

            if ($this->hasOption('f', 'force')) {
                return true;
            }

            return $this->confirm("Are you sure you want to run migration ?", false);
        }

        return true;
    }

    /**
     * Get migration path (either specified by '--path' option or default location).
     *
     * @return string
     */
    protected function getMigrationPath()
    {
        if (!is_null($targetPath = $this->getOption('path'))) {
            return BASE_PATH . '/' . $targetPath;
        }

        return $this->config->migrations->path;
    }

    /**
     * Get all of the migration paths.
     *
     * @return array
     */
    protected function getMigrationPaths()
    {
        // Here, we will check to see if a path option has been defined. If it has we will
        // use the path relative to the root of the installation folder so our database
        // migrations may be run for any customized path from within the application.
        if ($this->hasOption('path') && $this->getOption('path')) {
            return array_map(function ($path) {
                return BASE_PATH . '/' . $path;
            }, (array)$this->getOption('path'));
        }

        return array_merge(
            [$this->getMigrationPath()], $this->migrator->paths()
        );
    }
}


<?php

namespace Neutrino\Foundation\Cli\Tasks;

use Neutrino\Cli\Task;
use Neutrino\Support\Path;

/**
 * Class ViewClearTask
 *
 *  @package Neutrino\Foundation\Cli
 */
class ViewClearTask extends Task
{
    /**
     * Clear all compiled view files.
     *
     * @description Clear all compiled view files.
     */
    public function mainAction()
    {
        $compileDir = $this->config->view->compiled_path;

        $this->rm($compileDir);

        $this->info('Compiled views cleared!');
    }

    private function rm($path)
    {
        $path = Path::normalize($path);

        if (is_dir($path)) {
            foreach (glob($path . '/*') as $sub) {
                $this->rm($sub);
            }
        }

        @unlink($path);
    }
}


<?php

namespace Neutrino\HttpClient;

use Neutrino\Http\Standards\Method;

/**
 * Class Request
 *
 * @package Neutrino\HttpClient
 */
abstract class Request
{
    /*
     | Request Parameters
     */
    /** @var string */
    protected $method;

    /** @var Uri */
    protected $uri;

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

    /** @var \Neutrino\HttpClient\Header */
    protected $header;

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

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

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

    /** @var bool */
    protected $jsonRequest = false;

    /** @var bool */
    protected $fullResponse = false;

    /*
     | Response
     */
    /** @var \Neutrino\HttpClient\Response */
    protected $response;

    /**
     * Request constructor.
     *
     * @param \Neutrino\HttpClient\Header|null $header
     */
    public function __construct(Header $header = null)
    {
        $this->header = $header ?: new Header();
    }

    /**
     * Return the request response's
     *
     * @return Response
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Return builded url
     *
     * @return Uri
     */
    public function getUri()
    {
        if ($this->isPostMethod()) {
            return $this->uri;
        }

        return $this->buildUrl()->uri;
    }

    /**
     * Define request Url
     *
     * @param string|Uri $uri
     *
     * @return $this
     */
    public function setUri($uri)
    {
        $this->uri = new Uri($uri);

        return $this;
    }

    /**
     * Return request method
     *
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * Define request method
     *
     * @param string $method
     *
     * @return $this
     */
    public function setMethod($method)
    {
        $this->method = $method;

        return $this;
    }

    /**
     * Return request is based on POST (..., PUT, PATCH)
     *
     * @return bool
     */
    protected function isPostMethod()
    {
        $method = $this->getMethod();

        return $method == 'POST' || $method == 'PUT' || $method == 'PATCH';
    }

    /**
     * Return if request is a json request
     *
     * @return bool
     */
    public function isJsonRequest()
    {
        return $this->jsonRequest;
    }

    /**
     * Define if it's a json request
     *
     * @param bool $jsonRequest
     *
     * @return $this
     */
    public function setJsonRequest($jsonRequest)
    {
        $this->jsonRequest = $jsonRequest;

        return $this;
    }

    /**
     * @return bool
     */
    public function isFullResponse()
    {
        return $this->fullResponse;
    }

    /**
     * @param bool $fullResponse
     * @return Request
     */
    public function setFullResponse($fullResponse)
    {
        $this->fullResponse = $fullResponse;

        return $this;
    }

    /**
     * Retourne les parametres de la requete
     *
     * @return array
     */
    public function getParams()
    {
        return $this->params;
    }

    /**
     * Define the parameters
     *
     * @param array $parameters
     * @param bool $merge Merge or Erase current parameters
     *
     * @return $this
     */
    public function setParams($parameters, $merge = false)
    {
        if ($merge) {
            $this->params = array_merge($this->params, $parameters);
        } else {
            $this->params = $parameters;
        }

        return $this;
    }

    /**
     * Define a parameter
     *
     * @param string $name
     * @param string|array $value
     *
     * @return $this
     */
    public function setParam($name, $value)
    {
        $this->params[$name] = $value;

        return $this;
    }

    /**
     * Builds the url of the request, If the HTTP method is not [POST, PUT, PATCH]
     *
     * @return $this
     */
    protected function buildUrl()
    {
        if ($this->isPostMethod()) {
            return $this;
        }

        return $this->extendUrl($this->params);
    }

    /**
     * Add parametre to the URL
     *
     * @param array $parameters
     *
     * @return $this
     */
    public function extendUrl(array $parameters = [])
    {
        if (!empty($parameters)) {
            $this->uri->extendQuery($parameters);
        }

        return $this;
    }

    /**
     * Return the headers
     *
     * @return array
     */
    public function getHeaders()
    {
        return $this->header->getHeaders();
    }

    /**
     * Define the headers
     *
     * @param array $headers
     * @param bool $merge Merge or Erase current headers
     *
     * @return $this
     */
    public function setHeaders($headers, $merge = false)
    {
        $this->header->setHeaders($headers, $merge);

        return $this;
    }

    /**
     * Define a header
     *
     * @param string $name
     * @param string $value
     *
     * @return $this
     */
    public function setHeader($name, $value)
    {
        $this->header->set($name, $value);

        return $this;
    }

    /**
     * Return proxy setting
     *
     * @return array
     */
    public function getProxy()
    {
        return $this->proxy;
    }

    /**
     * Define proxy setting
     *
     * @param string $host
     * @param int $port
     * @param string $access
     *
     * @return $this
     */
    public function setProxy($host, $port = 8080, $access = null)
    {
        $this->proxy = [
            'host'   => $host,
            'port'   => $port,
            'access' => $access,
        ];

        return $this;
    }

    /**
     * Return the cookies
     *
     * @param bool $format Return the cookies formatted
     *
     * @return array|string
     */
    public function getCookies($format = false)
    {
        if ($format) {
            return implode(';', $this->cookies);
        }

        return $this->cookies;
    }

    /**
     * Define the cookies
     *
     * @param array $cookies
     * @param bool $merge Merge or Erase current cookies
     *
     * @return $this
     */
    public function setCookies($cookies, $merge = false)
    {
        if ($merge) {
            $this->cookies = array_merge($this->cookies, $cookies);
        } else {
            $this->cookies = $cookies;
        }

        return $this;
    }

    /**
     * Define a cookie
     *
     * @param null|string $key
     * @param string $value
     *
     * @return $this
     * @throws \InvalidArgumentException
     */
    public function setCookie($key, $value)
    {
        if (is_null($key)) {
            $this->cookies[] = $value;
        } else {
            $this->cookies[$key] = $value;
        }

        return $this;
    }

    /**
     * Return the options
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Define the options
     *
     * @param array $options
     * @param bool $merge Est-ce que l'on ajoute les options aux options existantes, ou les ecrases
     *
     * @return $this
     */
    public function setOptions($options, $merge = false)
    {
        if ($merge) {
            $this->options = array_merge($this->options, $options);
        } else {
            $this->options = $options;
        }

        return $this;
    }

    /**
     * Define an option
     *
     * @param string $name
     * @param mixed $value
     *
     * @return $this
     */
    public function setOption($name, $value)
    {
        $this->options[$name] = $value;

        return $this;
    }

    /**
     * Send the request & return the response
     *
     * @return \Neutrino\HttpClient\Response
     */
    public function send()
    {
        $this
            ->buildParams()
            ->buildProxy()
            ->buildCookies()
            ->buildHeaders();

        $this->response = new Response();

        return $this->makeCall();
    }

    /**
     * Build a HTTP request
     *
     * @param string $method
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function request($method, $uri, array $params = [], array $options = [])
    {
        $this
            ->setMethod($method)
            ->setUri($uri);

        if (!empty($params)) {
            $this->setParams($params, true);
        }
        if (!empty($options['headers'])) {
            $this->setHeaders($options['headers'], true);
        }
        if (isset($options['full'])) {
            $this->setFullResponse($options['full']);
        }
        if (isset($options['json'])) {
            $this->setJsonRequest($options['json']);
        }

        return $this;
    }

    /**
     * Build a HTTP [GET] request
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function get($uri, array $params = [], array $options = [])
    {
        return $this->request(Method::GET, $uri, $params, $options);
    }

    /**
     * Build a HTTP [HEAD] request
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function head($uri, array $params = [], array $options = [])
    {
        return $this->request(Method::HEAD, $uri, $params, $options);
    }

    /**
     * Build a HTTP [DELETE] request
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function delete($uri, array $params = [], array $options = [])
    {
        return $this->request(Method::DELETE, $uri, $params, $options);
    }

    /**
     * Build a HTTP [POST] request
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function post($uri, array $params = [], array $options = [])
    {
        return $this->request(Method::POST, $uri, $params, $options);
    }

    /**
     * Build a HTTP [PUT] request
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function put($uri, array $params = [], array $options = [])
    {
        return $this->request(Method::PUT, $uri, $params, $options);
    }

    /**
     * Build a HTTP [PATCH] request
     *
     * @param string $uri
     * @param array $params
     * @param array $options
     *
     * @return $this
     */
    public function patch($uri, array $params = [], array $options = [])
    {
        return $this->request(Method::PATCH, $uri, $params, $options);
    }

    /**
     * Define request timeout
     *
     * @param int $timeout
     *
     * @return $this
     */
    abstract public function setTimeout($timeout);

    /**
     * Disable Ssl
     *
     * @return $this
     */
    abstract public function disableSsl();

    /**
     * @return \Neutrino\HttpClient\Response
     */
    abstract protected function makeCall();

    /**
     * Build request params's
     *
     * @return $this
     */
    abstract protected function buildParams();

    /**
     * Build request header's
     *
     * @return $this
     */
    abstract protected function buildHeaders();

    /**
     * Build request proxy's
     *
     * @return $this
     */
    abstract protected function buildProxy();

    /**
     * Build request cookie's
     *
     * @return $this
     */
    abstract protected function buildCookies();
}


<?php

namespace Neutrino\Dotconst\Extensions;

use Neutrino\Dotconst\Helper;
use Neutrino\Support\Path;

/**
 * Class PhpDir
 *
 * @package Neutrino\Dotconst\Extensions
 */
class PhpDir extends Extension
{
    protected $identifier = 'php/dir(?::(/[\w\-. ]+))?(?:@(.+))?';

    /**
     * @param string $value
     * @param string $basePath
     *
     * @return string
     */
    public function parse($value, $basePath)
    {
        $match = $this->match($value);

        return Helper::normalizePath($basePath . DIRECTORY_SEPARATOR . (isset($match[1]) ? $match[1] : '') . (isset($match[2]) ? $match[2] : ''));
    }

    /**
     * @param string $value
     * @param string $basePath
     * @param string $compilePath
     *
     * @return string
     */
    public function compile($value, $basePath, $compilePath)
    {
        $match = $this->match($value);

        return "__DIR__ . '" . addslashes('/' . Path::findRelative($compilePath, $basePath) . '/' . (isset($match[1]) ? $match[1] : '')) . "'";
    }
}


<?php

namespace Neutrino\Database\Schema;

use Neutrino\Database\Schema\Exception\UnknownCommandException;
use Phalcon\Db\AdapterInterface as Db;
use Phalcon\Db\Column;
use Phalcon\Db\Index;
use Phalcon\Db\Reference;

/**
 * Class Blueprint
 *
 * @package Neutrino\Database\Schema
 */
class Blueprint
{
    /** @var string */
    protected $table;

    /** @var string FOR POSTGRESQL */
    protected $schema = null;

    /** @var Definition[] */
    protected $columns = [];

    /** @var Definition[] */
    protected $indexes = [];

    /** @var Definition[] */
    protected $references = [];

    /** @var Definition[] */
    protected $commands = [];

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

    /** @var int */
    protected $action;

    /**
     * Blueprint constructor.
     *
     * @param $table
     */
    public function __construct($table)
    {
        $this->table = $table;
    }

    /**
     * Execute the blueprint against the database.
     *
     * @param \Phalcon\Db\AdapterInterface               $db
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return bool
     * @throws \Exception
     */
    public function build(Db $db, DialectInterface $grammar)
    {
        switch ($this->action) {
            case 'create':
                return $this->buildCreate($db, $grammar);
            case 'update':
                return $this->buildUpdate($db, $grammar);
            case 'dropIfExists':
                return $this->buildDrop($db, true);
            case 'drop':
                return $this->buildDrop($db, false);
            case 'raw':
                return $this->buildRaw($db, $grammar);
            default:
                if (empty($this->action)) {
                    $message = "Blueprint must has action";
                } else {
                    $message = "Action '{$this->action}' not supported.";
                }
                throw new \RuntimeException($message);
        }
    }

    /**
     * @param \Phalcon\Db\AdapterInterface               $db
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return bool
     * @throws \Neutrino\Database\Schema\Exception\CommandException
     */
    protected function buildCreate(Db $db, DialectInterface $grammar)
    {
        $db->createTable($this->table, $this->schema, $this->buildTableDefinition($grammar));

        $this->runCommands($db, $grammar);

        return true;
    }

    /**
     * Build the table definition for table creation
     *
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return array
     */
    protected function buildTableDefinition(DialectInterface $grammar)
    {
        // Manage primary key / multiple primary keys
        $primaries = [];

        // Extract defined primary index
        foreach ($this->indexes as $key => $index) {
            if ($index->get('type') == 'PRIMARY') {
                foreach ($index->get('columns') as $column) {
                    $this->columns[$column]->primary();
                }

                unset($this->indexes[$key]);
            }
        }

        // Extract defined primary column
        foreach ($this->columns as $column) {
            if ($column->get('primary')) {
                $primaries[] = $column->get('name');

                unset($column['primary']);
            }
        }

        // Re-Build primary index
        if (($cprimaries = count($primaries)) === 1) {
            $this->columns[array_shift($primaries)]->primary();
        } elseif ($cprimaries > 0) {
            $this->primary($primaries);
        }

        // Build table definition
        $definition = [];

        foreach ($this->columns as $column) {
            $definition['columns'][] = $this->fluentToColumn($column, $grammar);
        }

        foreach ($this->indexes as $index) {
            $definition['indexes'][] = $this->fluentToIndex($index, $grammar);
        }

        foreach ($this->references as $reference) {
            $definition['references'][] = $this->fluentToReference($reference);
        }

        foreach ($this->options as $name => $value) {
            $definition['options'][strtoupper($name)] = $value;
        }

        return $definition;
    }

    /**
     * @param \Phalcon\Db\AdapterInterface               $db
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return bool
     * @throws \Exception
     */
    protected function buildUpdate(Db $db, DialectInterface $grammar)
    {
        $this->buildCommands($db);

        $this->runCommands($db, $grammar);

        return true;
    }

    /**
     * @param \Phalcon\Db\AdapterInterface               $db
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return bool
     * @throws \Exception
     */
    protected function buildRaw(Db $db, DialectInterface $grammar)
    {
        $this->runCommands($db, $grammar);

        return true;
    }

    /**
     * Build all columns, indexes, references, to commands
     *
     * @param \Phalcon\Db\AdapterInterface $db
     */
    protected function buildCommands(Db $db)
    {
        $columns = $db->describeColumns($this->table, $this->schema);
        foreach ($this->columns as $column) {
            $this->buildIndexAndForeignFromFluentColumn($column);

            foreach ($columns as $c) {
                if ($c->getName() === $column->get('name')) {
                    $this->addCommand('modifyColumn', ['column' => $column, 'from' => $c]);
                    continue 2;
                }
            }

            $this->addCommand('addColumn', ['column' => $column]);
        }

        foreach ($this->indexes as $index) {
            $command = $index->get('type') === 'PRIMARY'
                ? 'addPrimary'
                : 'addIndex';

            $this->addCommand($command, ['index' => $index]);
        }

        foreach ($this->references as $reference) {
            $this->addCommand('addForeign', ['reference' => $reference]);
        }
    }

    /**
     * @param \Phalcon\Db\AdapterInterface $connection
     * @param bool                         $ifExist
     *
     * @return bool
     */
    protected function buildDrop(Db $connection, $ifExist = false)
    {
        return $connection->dropTable($this->table, $this->schema, $ifExist);
    }

    /**
     * @param \Phalcon\Db\AdapterInterface               $db
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @throws \Neutrino\Database\Schema\Exception\CommandException
     * @throws \Neutrino\Database\Schema\Exception\UnknownCommandException
     */
    protected function runCommands(Db $db, DialectInterface $grammar)
    {
        $table = $this->table;
        $schema = $this->schema;

        foreach ($this->commands as $command) {
            switch ($command->get('name')) {
                case 'addColumn':
                    $db->addColumn(
                        $table,
                        $schema,
                        $this->fluentToColumn($command->get('column'), $grammar)
                    );
                    break;
                case 'renameColumn':
                    $columns = $db->describeColumns($table, $schema);
                    $from = null;
                    foreach ($columns as $column) {
                        if ($column->getName() === $command->get('from')) {
                            $from = $column;
                            break;
                        }
                    }
                    $definition = [
                        'type'          => $from->getType(),
                        'notNull'       => $from->isNotNull(),
                        'primary'       => $from->isPrimary(),
                        'autoIncrement' => $from->isAutoIncrement(),
                        'numeric'       => $from->isNumeric(),
                    ];

                    if (($typeReference = $from->getTypeReference()) !== -1) {
                        $definition['typeReference'] = $typeReference;
                    }
                    if (($typeValues = $from->getTypeValues()) !== -1) {
                        $definition['typeValues'] = $typeValues;
                    }
                    if (!empty($size = $from->getSize())) {
                        $definition['size'] = $size;
                    }
                    if (!is_null($default = $from->getDefault())) {
                        $definition['default'] = $default;
                    }
                    if (!empty($scale = $from->getScale())) {
                        $definition['scale'] = $scale;
                    }

                    $to = new Column($command->get('to'), $definition);

                    $db->modifyColumn($table, $schema, $to, $from);
                    break;
                case 'modifyColumn':
                    $db->modifyColumn(
                        $table,
                        $schema,
                        $this->fluentToColumn($command->get('column'), $grammar),
                        $command->get('from')
                    );
                    break;
                case 'addPrimary':
                    $db->addPrimaryKey(
                        $table,
                        $schema,
                        $this->fluentToIndex($command->get('index'), $grammar)
                    );
                    break;
                case 'addIndex':
                    $db->addIndex(
                        $table,
                        $schema,
                        $this->fluentToIndex($command->get('index'), $grammar)
                    );
                    break;
                case 'addForeign':
                    $db->addForeignKey(
                        $table,
                        $schema,
                        $this->fluentToReference($command->get('reference'))
                    );
                    break;
                case 'dropColumn':
                    $db->dropColumn(
                        $table,
                        $schema,
                        $command->get('column')
                    );
                    break;
                case 'dropForeign':
                    foreach ((array)$command->get('reference') as $reference) {
                        $db->dropForeignKey($table, $schema, $reference);
                    }
                    break;
                case 'dropIndex':
                    foreach ((array)$command->get('index') as $index) {
                        $db->dropIndex($table, $schema, $index);
                    }
                    break;
                case 'dropPrimary':
                    $db->dropPrimaryKey($table, $schema);
                    break;
                case 'rename':
                    $db->execute($grammar->renameTable($table, $command->get('to'), $schema));
                    break;
                case 'sql':
                    $db->execute($command->get('sql'));
                    break;
                default:
                    throw new UnknownCommandException($command);
            }
        }
    }

    /**
     * @param \Neutrino\Database\Schema\Definition $column
     */
    protected function buildIndexAndForeignFromFluentColumn(Definition $column)
    {
        $attributes = $column->getAttributes();

        if (isset($attributes['unique']) && $attributes['unique']) {
            $this->unique($column->get('name'), is_bool($attributes['unique']) ? null : $attributes['unique']);

            unset($column['unique']);
        } elseif (isset($attributes['index']) && $attributes['index']) {
            $this->index($column->get('name'), is_bool($attributes['index']) ? null : $attributes['index']);

            unset($column['index']);
        } elseif ($column->get('foreign')) {
            $this->foreign($column->get('name'))
                ->on($column->get('on'))
                ->references($column->get('references'))
                ->onUpdate($column->get('onUpdate'))
                ->onDelete($column->get('onDelete'));

            unset($column['foreign']);
            unset($column['on']);
            unset($column['references']);
            unset($column['onUpdate']);
            unset($column['onDelete']);
        }
    }

    /**
     * Transform a Fluent(Column) to a \Phalcon\Db\Column
     *
     * @param \Neutrino\Database\Schema\Definition       $column
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return \Phalcon\Db\Column
     */
    protected function fluentToColumn(Definition $column, DialectInterface $grammar)
    {
        $attributes = $column->getAttributes();

        $this->buildIndexAndForeignFromFluentColumn($column);

        $types = $grammar->getType($column);

        if (isset($attributes['type'])) {
            unset($attributes['type']);
        }
        if (isset($attributes['typeReference'])) {
            unset($attributes['typeReference']);
        }
        if (isset($attributes['typeValues'])) {
            unset($attributes['typeValues']);
        }

        if (isset($attributes['nullable'])) {
            $attributes['notNull'] = !$attributes['nullable'];
        } else {
            $attributes['notNull'] = true;
        }

        return new Column($column->get('name'), array_merge($types, $attributes));
    }

    /**
     * Transform a Fluent(Index) to a \Phalcon\Db\Index
     *
     * @param \Neutrino\Database\Schema\Definition                   $index
     * @param \Neutrino\Database\Schema\DialectInterface $grammar
     *
     * @return \Phalcon\Db\Index
     */
    protected function fluentToIndex(Definition $index, DialectInterface $grammar)
    {
        $name = $index->get('name') ?: $this->createIndexName($index->get('type'), $index->get('columns'));

        return new Index($name, $index->get('columns'), $grammar->getIndexType($index));
    }

    /**
     * Transform a Fluent(Reference) to a \Phalcon\Db\Reference
     *
     * @param \Neutrino\Database\Schema\Definition $index
     *
     * @return \Phalcon\Db\Reference
     */
    protected function fluentToReference(Definition $index)
    {
        $columns = (array)$index->get('columns');
        $references = (array)$index->get('references');

        $name = $index->get('name') ?: $this->createReferenceName($columns, $index->get('on'), $references);

        $definition = [
          'columns'           => $columns,
          'referencedTable'   => $index->get('on'),
          'referencedColumns' => $references,
        ];

        if ($index->get('onDelete')) {
            $definition['onDelete'] = $index->get('onDelete');
        }
        if ($index->get('onUpdate')) {
            $definition['onUpdate'] = $index->get('onUpdate');
        }

        return new Reference($name, $definition);
    }

    /**
     * Indicate that the table needs to be temporary.
     *
     * @return $this
     */
    public function temporary()
    {
        return $this->option(__FUNCTION__, true);
    }

    /**
     * Indicate that the table needs to be created.
     *
     * @return $this
     */
    public function create()
    {
        $this->action = __FUNCTION__;

        return $this;
    }

    /**
     * Indicate that the table needs to be updated.
     *
     * @return $this
     */
    public function update()
    {
        $this->action = __FUNCTION__;

        return $this;
    }

    /**
     * Indicate that the table should be dropped.
     *
     * @return $this
     */
    public function drop()
    {
        $this->action = __FUNCTION__;

        return $this;
    }

    /**
     * Indicate that the table should be dropped if it exists.
     *
     * @return $this
     */
    public function dropIfExists()
    {
        $this->action = __FUNCTION__;

        return $this;
    }

    /**
     * Indicate that blueprint run raw SQL
     *
     * @return $this
     */
    public function raw()
    {
        $this->action = __FUNCTION__;

        return $this;
    }

    /**
     * Indicate that the given columns should be dropped.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dropColumn($column)
    {
        return $this->addCommand(__FUNCTION__, ['column' => $column]);
    }

    /**
     * Indicate that the given columns should be dropped.
     *
     * @param  string[] $columns
     */
    public function dropColumns($columns)
    {
        foreach ($columns as $column) {
            $this->dropColumn($column);
        }
    }

    /**
     * Indicate that the given columns should be renamed.
     *
     * @param  string $from
     * @param  string $to
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function renameColumn($from, $to)
    {
        return $this->addCommand(__FUNCTION__, ['from' => $from, 'to' => $to]);
    }

    /**
     * Indicate that the given primary key should be dropped.
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dropPrimary()
    {
        return $this->addCommand(__FUNCTION__);
    }

    /**
     * Indicate that the given unique key should be dropped.
     *
     * @param  string|array $index
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dropUnique($index)
    {
        return $this->dropIndex($index);
    }

    /**
     * Indicate that the given index should be dropped.
     *
     * @param  string|array $index
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dropIndex($index)
    {
        return $this->addCommand(__FUNCTION__, ['index' => $index]);
    }

    /**
     * Indicate that the given foreign key should be dropped.
     *
     * @param  string|array $reference
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dropForeign($reference)
    {
        return $this->addCommand(__FUNCTION__, ['reference' => $reference]);
    }

    /**
     * Indicate that the timestamp columns should be dropped.
     *
     * @return void
     */
    public function dropTimestamps()
    {
        $this->dropColumns(['created_at', 'updated_at']);
    }

    /**
     * Indicate that the timestamp columns should be dropped.
     *
     * @return void
     */
    public function dropTimestampsTz()
    {
        $this->dropTimestamps();
    }

    /**
     * Indicate that the soft delete column should be dropped.
     *
     * @return void
     */
    public function dropSoftDeletes()
    {
        $this->dropColumn('deleted_at');
    }

    /**
     * Indicate that the soft delete column should be dropped.
     *
     * @return void
     */
    public function dropSoftDeletesTz()
    {
        $this->dropSoftDeletes();
    }

    /**
     * Indicate that the remember token column should be dropped.
     *
     * @return void
     */
    public function dropRememberToken()
    {
        $this->dropColumn('remember_token');
    }

    /**
     * Define schema. FOR POSTGRESQL.
     *
     * @param  string $schema
     *
     * @return $this
     */
    public function schema($schema)
    {
        $this->schema = $schema;

        return $this;
    }

    /**
     * Rename the table to a given name.
     *
     * @param  string $to
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function rename($to)
    {
        return $this->addCommand(__FUNCTION__, ['to' => $to]);
    }

    /**
     * Specify the primary key(s) for the table.
     *
     * @param  string|array $columns
     * @param  string|null  $name
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function primary($columns, $name = null)
    {
        return $this->addIndex(__FUNCTION__, $columns, $name);
    }

    /**
     * Specify a unique index for the table.
     *
     * @param  string|array $columns
     * @param  string|null  $name
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function unique($columns, $name = null)
    {
        return $this->addIndex(__FUNCTION__, $columns, $name);
    }

    /**
     * Specify an index for the table.
     *
     * @param  string|array $columns
     * @param  string|null  $name
     * @param  string       $type Type of index
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function index($columns, $name = null, $type = __FUNCTION__)
    {
        return $this->addIndex($type, $columns, $name);
    }

    /**
     * Specify a foreign key for the table.
     *
     * @param  string|array $columns
     * @param  string|null  $name
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function foreign($columns, $name = null)
    {
        return $this->addForeign($columns, $name);
    }

    /**
     * Create a new auto-incrementing integer (4-byte) column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function increments($column)
    {
        return $this->unsignedInteger($column, true);
    }

    /**
     * Create a new auto-incrementing tiny integer (1-byte) column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function tinyIncrements($column)
    {
        return $this->unsignedTinyInteger($column, true);
    }

    /**
     * Create a new auto-incrementing small integer (2-byte) column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function smallIncrements($column)
    {
        return $this->unsignedSmallInteger($column, true);
    }

    /**
     * Create a new auto-incrementing medium integer (3-byte) column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function mediumIncrements($column)
    {
        return $this->unsignedMediumInteger($column, true);
    }

    /**
     * Create a new auto-incrementing big integer (8-byte) column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function bigIncrements($column)
    {
        return $this->unsignedBigInteger($column, true);
    }

    /**
     * Create a new char column on the table.
     *
     * @param  string $column
     * @param  int    $length
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function char($column, $length = null)
    {
        return $this->addColumn(__FUNCTION__, $column, ['size' => $length ?: Builder::$defaultStringLength]);
    }

    /**
     * Create a new string column on the table.
     *
     * @param  string   $column
     * @param  int|null $length
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function string($column, $length = null)
    {
        return $this->addColumn(__FUNCTION__, $column, ['size' => $length ?: Builder::$defaultStringLength]);
    }

    /**
     * Create a new text column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function text($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new medium text column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function mediumText($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new long text column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function longText($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * @param string $type
     * @param string $column
     * @param bool   $autoIncrement
     * @param bool   $unsigned
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    protected function addInteger($type, $column, $autoIncrement = false, $unsigned = false)
    {
        $column = $this->addColumn($type, $column, ['autoIncrement' => $autoIncrement, 'unsigned' => $unsigned]);

        if ($autoIncrement) {
            $column->primary();
        }

        return $column;
    }

    /**
     * Create a new integer (4-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     * @param  bool   $unsigned
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function integer($column, $autoIncrement = false, $unsigned = false)
    {
        return $this->addInteger(__FUNCTION__, $column, $autoIncrement, $unsigned);
    }

    /**
     * Create a new tiny integer (1-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     * @param  bool   $unsigned
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function tinyInteger($column, $autoIncrement = false, $unsigned = false)
    {
        return $this->addInteger(__FUNCTION__, $column, $autoIncrement, $unsigned);
    }

    /**
     * Create a new small integer (2-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     * @param  bool   $unsigned
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function smallInteger($column, $autoIncrement = false, $unsigned = false)
    {
        return $this->addInteger(__FUNCTION__, $column, $autoIncrement, $unsigned);
    }

    /**
     * Create a new medium integer (3-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     * @param  bool   $unsigned
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function mediumInteger($column, $autoIncrement = false, $unsigned = false)
    {
        return $this->addInteger(__FUNCTION__, $column, $autoIncrement, $unsigned);
    }

    /**
     * Create a new big integer (8-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     * @param  bool   $unsigned
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function bigInteger($column, $autoIncrement = false, $unsigned = false)
    {
        return $this->addInteger(__FUNCTION__, $column, $autoIncrement, $unsigned);
    }

    /**
     * Create a new unsigned integer (4-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function unsignedInteger($column, $autoIncrement = false)
    {
        return $this->integer($column, $autoIncrement, true);
    }

    /**
     * Create a new unsigned tiny integer (1-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function unsignedTinyInteger($column, $autoIncrement = false)
    {
        return $this->tinyInteger($column, $autoIncrement, true);
    }

    /**
     * Create a new unsigned small integer (2-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function unsignedSmallInteger($column, $autoIncrement = false)
    {
        return $this->smallInteger($column, $autoIncrement, true);
    }

    /**
     * Create a new unsigned medium integer (3-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function unsignedMediumInteger($column, $autoIncrement = false)
    {
        return $this->mediumInteger($column, $autoIncrement, true);
    }

    /**
     * Create a new unsigned big integer (8-byte) column on the table.
     *
     * @param  string $column
     * @param  bool   $autoIncrement
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function unsignedBigInteger($column, $autoIncrement = false)
    {
        return $this->bigInteger($column, $autoIncrement, true);
    }

    /**
     * Create a new float column on the table.
     *
     * @param  string $column
     * @param  int    $scale
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function float($column, $scale = null)
    {
        return $this->addColumn(__FUNCTION__, $column, is_int($scale) ? ['scale' => $scale] : []);
    }

    /**
     * Create a new double column on the table.
     *
     * @param  string $column
     * @param  int    $scale
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function double($column, $scale = null)
    {
        return $this->addColumn(__FUNCTION__, $column, is_int($scale) ? ['scale' => $scale] : []);
    }

    /**
     * Create a new decimal column on the table.
     *
     * @param  string $column
     * @param  int    $scale
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function decimal($column, $scale = null)
    {
        return $this->addColumn(__FUNCTION__, $column, is_int($scale) ? ['scale' => $scale] : []);
    }

    /**
     * Create a new boolean column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function boolean($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new enum column on the table.
     *
     * @param  string $column
     * @param  array  $allowed
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function enum($column, array $allowed)
    {
        return $this->addColumn(__FUNCTION__, $column, ['values' => $allowed]);
    }

    /**
     * Create a new json column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function json($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new jsonb column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function jsonb($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new date column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function date($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new date-time column on the table.
     *
     * @param  string $column
     * @param  int    $precision
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dateTime($column, $precision = 0)
    {
        return $this->addColumn(__FUNCTION__, $column, ['precision' => $precision]);
    }

    /**
     * /**
     * Create a new date-time column (with time zone) on the table.
     *
     * @param  string $column
     * @param  int    $precision
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function dateTimeTz($column, $precision = 0)
    {
        return $this->addColumn(__FUNCTION__, $column, ['precision' => $precision]);
    }

    /**
     * Create a new time column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function time($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new time column (with time zone) on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function timeTz($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new timestamp column on the table.
     *
     * @param  string $column
     * @param  int    $precision
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function timestamp($column, $precision = 0)
    {
        return $this->addColumn(__FUNCTION__, $column, ['precision' => $precision]);
    }

    /**
     * Create a new timestamp (with time zone) column on the table.
     *
     * @param  string $column
     * @param  int    $precision
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function timestampTz($column, $precision = 0)
    {
        return $this->addColumn(__FUNCTION__, $column, ['precision' => $precision]);
    }

    /**
     * Add creation and update timestamps to the table.
     *
     * @param  int $precision
     *
     * @return void
     */
    public function timestamps($precision = 0)
    {
        $this->timestamp('created_at', $precision)->default('CURRENT_TIMESTAMP');

        $this->timestamp('updated_at', $precision)->default('CURRENT_TIMESTAMP')->onUpdate('CURRENT_TIMESTAMP');
    }

    /**
     * Add nullable creation and update timestamps to the table.
     *
     * @param  int $precision
     *
     * @return void
     */
    public function nullableTimestamps($precision = 0)
    {
        $this->timestamps($precision);

        $this->columns['created_at']->nullable();
        $this->columns['updated_at']->nullable();
    }

    /**
     * Add creation and update timestampTz columns to the table.
     *
     * @param  int $precision
     *
     * @return void
     */
    public function timestampsTz($precision = 0)
    {
        $this->timestampTz('created_at', $precision)->default('CURRENT_TIMESTAMP');

        $this->timestampTz('updated_at', $precision)->default('CURRENT_TIMESTAMP')->onUpdate('CURRENT_TIMESTAMP');
    }

    /**
     * Add nullable creation and update timestampTz to the table.
     *
     * @param  int $precision
     *
     * @return void
     */
    public function nullableTimestampsTz($precision = 0)
    {
        $this->timestampsTz($precision);

        $this->columns['created_at']->nullable();
        $this->columns['updated_at']->nullable();
    }

    /**
     * Add a "deleted at" timestamp for the table.
     *
     * @param  string $column
     * @param  int    $precision
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function softDeletes($column = 'deleted_at', $precision = 0)
    {
        return $this->timestamp($column, $precision)->nullable();
    }

    /**
     * Add a "deleted at" timestampTz for the table.
     *
     * @param  string $column
     * @param  int    $precision
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function softDeletesTz($column = 'deleted_at', $precision = 0)
    {
        return $this->timestampTz($column, $precision)->nullable();
    }

    /**
     * Create a new binary column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function binary($column)
    {
        return $this->blob($column);
    }

    /**
     * Create a new binary column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function blob($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new tiny binary column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function tinyBlob($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new medium binary column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function mediumBlob($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new long binary column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function longBlob($column)
    {
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new uuid column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function uuid($column)
    {
        /* @todo Uuid behavior */
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new IP address column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function ipAddress($column)
    {
        /* @todo IpAddress behavior */
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Create a new MAC address column on the table.
     *
     * @param  string $column
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function macAddress($column)
    {
        /* @todo MacAddress behavior */
        return $this->addColumn(__FUNCTION__, $column);
    }

    /**
     * Add the proper columns for a polymorphic table.
     *
     * @param  string      $name
     * @param  string|null $indexName
     *
     * @return void
     */
    public function morphs($name, $indexName = null)
    {
        $this->unsignedInteger("{$name}_id");

        $this->string("{$name}_type");

        $this->index(["{$name}_id", "{$name}_type"], $indexName);
    }

    /**
     * Add nullable columns for a polymorphic table.
     *
     * @param  string      $name
     * @param  string|null $indexName
     *
     * @return void
     */
    public function nullableMorphs($name, $indexName = null)
    {
        $this->unsignedInteger("{$name}_id")->nullable();

        $this->string("{$name}_type")->nullable();

        $this->index(["{$name}_id", "{$name}_type"], $indexName);
    }

    /**
     * Adds the `remember_token` column to the table.
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function rememberToken()
    {
        return $this->string('remember_token', 100)->nullable();
    }

    /**
     * Add raw sql to execute
     *
     * @param string $sql
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    public function sql($sql)
    {
        return $this->addCommand(__FUNCTION__, ['sql' => $sql]);
    }

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

    /**
     * @param string $name
     * @param string $value
     *
     * @return $this
     */
    public function option($name, $value)
    {
        $this->options[$name] = $value;

        return $this;
    }

    /**
     * Get the table the blueprint describes.
     *
     * @return string
     */
    public function getTable()
    {
        return $this->table;
    }

    /**
     * Get the columns on the blueprint.
     *
     * @return array
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Get the commands on the blueprint.
     *
     * @return array
     */
    public function getCommands()
    {
        return $this->commands;
    }

    /**
     * Get the indexes on the blueprint.
     *
     * @return array
     */
    public function getIndexes()
    {
        return $this->indexes;
    }

    /**
     * Get the references on the blueprint.
     *
     * @return array
     */
    public function getReferences()
    {
        return $this->references;
    }

    /**
     * Add a new column to the blueprint.
     *
     * @param  int    $type
     * @param  string $name
     * @param  array  $parameters
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    protected function addColumn($type, $name, array $parameters = [])
    {
        return $this->columns[$name] = new Definition(array_merge([
            'name' => $name,
            "type" => $type,
        ], $parameters));
    }

    /**
     * Add a new command to the blueprint.
     *
     * @param  string $name
     * @param  array  $parameters
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    protected function addCommand($name, array $parameters = [])
    {
        $this->commands[] = $command = $this->createCommand($name, $parameters);

        return $command;
    }

    /**
     * Create a new Fluent command.
     *
     * @param  string $name
     * @param  array  $parameters
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    protected function createCommand($name, array $parameters = [])
    {
        return new Definition(array_merge(['name' => $name], $parameters));
    }

    /**
     * Add a new command to the blueprint.
     *
     * @param  string          $type
     * @param  string|string[] $columns
     * @param  string|null     $name
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    protected function addIndex($type, $columns, $name = null)
    {
        $columns = (array)$columns;

        // If no name was specified for this index, we will create one using a basic
        // convention of the table name, followed by the columns, followed by an
        // index type, such as primary or index, which makes the index unique.
        $name = $name ?: $this->createIndexName($type, $columns);

        return $this->indexes[] = new Definition(['name' => $name, 'columns' => $columns, 'type' => strtoupper($type)]);
    }

    /**
     * Add a new command to the blueprint.
     *
     * @param  string|string[] $columns
     * @param  string|null     $name
     *
     * @return \Neutrino\Database\Schema\Definition
     */
    protected function addForeign($columns, $name = null)
    {
        $columns = (array)$columns;

        // If no name was specified for this index, we will create one using a basic
        // convention of the table name, followed by the columns, followed by an
        // index type, such as primary or index, which makes the index unique.

        return $this->references[] = new Definition(['name' => $name, 'columns' => $columns, 'type' => 'foreign']);
    }

    /**
     * Create a default index name for the table.
     *
     * @param  string $type
     * @param  array  $columns
     *
     * @return string
     */
    protected function createIndexName($type, array $columns)
    {
        $index = strtolower(implode('_', array_filter([$this->table, implode('_', $columns), $type])));

        return trim(str_replace(['-', '.'], '_', $index), '_');
    }

    /**
     * Create a default index name for the table.
     *
     * @param  array  $columns
     * @param  string $on
     * @param  array  $references
     *
     * @return string
     */
    protected function createReferenceName(array $columns, $on, array $references)
    {
        $strColumns = implode('_', $columns);
        $strReferences = implode('_', $references);

        $rawReferenceName = [$this->table, $strColumns, 'foreign', $on, $strReferences];

        $rawReferenceName = array_map(function ($value) {
            return trim(str_replace(['-', '.'], '_', $value), '_');
        }, $rawReferenceName);

        $rawReferenceName = array_filter($rawReferenceName);

        $index = strtolower(trim(implode('_', $rawReferenceName), '_'));

        return $index;
    }
}


<?php

namespace Neutrino\Support;

class Arr
{
    /**
     * Determine whether the given value is array accessible.
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function accessible($value)
    {
        return is_array($value) || $value instanceof \ArrayAccess;
    }

    /**
     * Add an element to an array using "dot" notation if it doesn't exist.
     *
     * @param array  $array
     * @param string $key
     * @param mixed  $value
     *
     * @return array
     */
    public static function add($array, $key, $value)
    {
        if (is_null(self::get($array, $key))) {
            self::set($array, $key, $value);
        }

        return $array;
    }

    /**
     * Collapse an array of arrays into a single array.
     *
     * @param \ArrayAccess|array $array
     *
     * @return array
     */
    public static function collapse($array)
    {
        $results = [];

        foreach ($array as $values) {
            if (!self::accessible($values)) {
                continue;
            }

            $results = array_merge($results, $values);
        }

        return $results;
    }

    /**
     * Divide an array into two arrays. One with keys and the other with values.
     *
     * @param array $array
     *
     * @return array
     */
    public static function divide($array)
    {
        return [array_keys($array), array_values($array)];
    }

    /**
     * Flatten a multi-dimensional associative array with dots.
     *
     * @param array  $array
     * @param string $prepend
     *
     * @return array
     */
    public static function dot($array, $prepend = '')
    {
        $results = [];

        foreach ($array as $key => $value) {
            if (is_array($value) && !empty($value)) {
                $results = array_merge($results, self::dot($value, $prepend . $key . '.'));
            } else {
                $results[$prepend . $key] = $value;
            }
        }

        return $results;
    }

    /**
     * Get all of the given array except for a specified array of items.
     *
     * @param array        $array
     * @param array|string $keys
     *
     * @return array
     */
    public static function except($array, $keys)
    {
        self::forget($array, $keys);

        return $array;
    }

    /**
     * Determine if the given key exists in the provided array.
     *
     * @param \ArrayAccess|array $array
     * @param string|int         $key
     *
     * @return bool
     */
    public static function exists($array, $key)
    {
        if (is_array($array)) {
            return isset($array[$key]) || array_key_exists($key, $array);
        }

        return $array->offsetExists($key);
    }

    /**
     * Return the first element in an array passing a given truth test.
     *
     * @param array         $array
     * @param callable|null $callback
     * @param mixed         $default
     *
     * @return mixed
     */
    public static function first($array, $callback = null, $default = null)
    {
        if (is_null($callback)) {
            return empty($array) ? Obj::value($default) : reset($array);
        }

        foreach ($array as $key => $value) {
            if (call_user_func($callback, $key, $value)) {
                return $value;
            }
        }

        return Obj::value($default);
    }

    /**
     * Return the last element in an array passing a given truth test.
     *
     * @param array         $array
     * @param callable|null $callback
     * @param mixed         $default
     *
     * @return mixed
     */
    public static function last($array, callable $callback = null, $default = null)
    {
        if (is_null($callback)) {
            return empty($array) ? Obj::value($default) : end($array);
        }

        return self::first(array_reverse($array, true), $callback, $default);
    }

    /**
     * Flatten a multi-dimensional array into a single level.
     *
     * @param array $array
     * @param int   $depth
     *
     * @return array
     */
    public static function flatten($array, $depth = INF)
    {
        return array_reduce($array, function ($result, $item) use ($depth) {
            if (!is_array($item)) {
                return array_merge($result, [$item]);
            } elseif ($depth === 1) {
                return array_merge($result, array_values($item));
            } else {
                return array_merge($result, self::flatten($item, $depth - 1));
            }
        }, []);
    }

    /**
     * Remove one or many array items from a given array using "dot" notation.
     *
     * @param array        $array
     * @param array|string $keys
     *
     * @return void
     */
    public static function forget(&$array, $keys)
    {
        $original = &$array;
        $keys     = (array)$keys;
        if (count($keys) === 0) {
            return;
        }
        foreach ($keys as $key) {
            // if the exact key exists in the top-level, remove it
            if (self::exists($array, $key)) {
                unset($array[$key]);
                continue;
            }
            $parts = explode('.', $key);
            // clean up before each pass
            $array = &$original;
            while (count($parts) > 1) {
                $part = array_shift($parts);
                if (isset($array[$part]) && is_array($array[$part])) {
                    $array = &$array[$part];
                } else {
                    continue 2;
                }
            }
            unset($array[array_shift($parts)]);
        }
    }

    /**
     * Get an item from an array.
     *
     * @param array  $array
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public static function read($array, $key, $default = null)
    {
        if (is_null($key)) {
            return Obj::value($default);
        }

        return self::exists($array, $key) ? $array[$key] : Obj::value($default);
    }

    /**
     * Get an item, no null, from an array.
     *
     * @param array  $array
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public static function fetch($array, $key, $default = null)
    {
        if (is_null($key)) {
            return Obj::value($default);
        }

        return isset($array[$key]) ? $array[$key] : Obj::value($default);
    }

    /**
     * Get an item from an array using "dot" notation.
     *
     * @param \ArrayAccess|array $array
     * @param string             $key
     * @param mixed              $default
     *
     * @return mixed
     */
    public static function get($array, $key, $default = null)
    {
        if (!self::accessible($array)) {
            return Obj::value($default);
        }
        if (is_null($key)) {
            return $array;
        }
        if (!is_array($key)) {
            if (self::exists($array, $key)) {
                return $array[$key];
            }

            $keys = explode('.', $key);
        } else {
            $keys = $key;
        }
        foreach ($keys as $segment) {
            if (self::accessible($array) && self::exists($array, $segment)) {
                $array = $array[$segment];
            } else {
                return Obj::value($default);
            }
        }

        return $array;
    }

    /**
     * Check if an item exists in an array using "dot" notation.
     *
     * @param \ArrayAccess|array $array
     * @param string|array       $keys
     *
     * @return bool
     */
    public static function has($array, $keys)
    {
        if (is_null($keys)) {
            return false;
        }
        $keys = (array)$keys;
        if (!$array) {
            return false;
        }
        if ($keys === []) {
            return false;
        }
        foreach ($keys as $key) {
            $subKeyArray = $array;
            if (self::exists($array, $key)) {
                continue;
            }
            foreach (explode('.', $key) as $segment) {
                if (self::accessible($subKeyArray) && self::exists($subKeyArray, $segment)) {
                    $subKeyArray = $subKeyArray[$segment];
                } else {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Determines if an array is associative.
     *
     * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
     *
     * @param array $array
     *
     * @return bool
     */
    public static function isAssoc($array)
    {
        return array_values($array) !== $array;
    }

    /**
     * Get a subset of the items from the given array.
     *
     * @param array        $array
     * @param array|string $keys
     *
     * @return array
     */
    public static function only($array, $keys)
    {
        return array_intersect_key($array, array_flip((array)$keys));
    }

    /**
     * Pluck an array of values from an array.
     *
     * @param \ArrayAccess|array $array
     * @param string|array       $value
     * @param string|array|null  $key
     *
     * @return array
     */
    public static function pluck($array, $value, $key = null)
    {
        $results = [];

        list($value, $key) = self::explodePluckParameters($value, $key);

        foreach ($array as $item) {
            $itemValue = self::get($item, $value);

            // If the key is "null", we will just append the value to the array and keep
            // looping. Otherwise we will key the array using the value of the key we
            // received from the developer. Then we'll return the final array form.
            if (is_null($key)) {
                $results[] = $itemValue;
            } else {
                $itemKey = self::get($item, $key);

                $results[$itemKey] = $itemValue;
            }
        }

        return $results;
    }

    /**
     * Push an item onto the beginning of an array.
     *
     * @param array $array
     * @param mixed $value
     * @param mixed $key
     *
     * @return array
     */
    public static function prepend($array, $value, $key = null)
    {
        if (is_null($key)) {
            array_unshift($array, $value);
        } else {
            $array = [$key => $value] + $array;
        }

        return $array;
    }

    /**
     * Get a value from the array, and remove it.
     *
     * @param array  $array
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public static function pull(&$array, $key, $default = null)
    {
        $value = self::get($array, $key, $default);

        self::forget($array, $key);

        return $value;
    }

    /**
     * Set an array item to a given value using "dot" notation.
     *
     * If no key is given to the method, the entire array will be replaced.
     *
     * @param array  $array
     * @param string $key
     * @param mixed  $value
     *
     * @return array
     */
    public static function set(&$array, $key, $value)
    {
        if (is_null($key)) {
            return $array = $value;
        }

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

        while (count($keys) > 1) {
            $key = array_shift($keys);

            // If the key doesn't exist at this depth, we will just create an empty array
            // to hold the next value, allowing us to create the arrays to hold final
            // values at the correct depth. Then we'll keep digging into the array.
            if (!isset($array[$key]) || !is_array($array[$key])) {
                $array[$key] = [];
            }

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

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

        return $array;
    }

    /**
     * Recursively sort an array by keys and values.
     *
     * @param array $array
     * @param null  $sort_flags : @see http://php.net/manual/en/function.sort.php
     *
     * @return array
     */
    public static function sortRecursive($array, $sort_flags = null)
    {
        foreach ($array as &$value) {
            if (is_array($value)) {
                $value = self::sortRecursive($value);
            }
        }

        if (self::isAssoc($array)) {
            ksort($array, $sort_flags);
        } else {
            sort($array, $sort_flags);
        }

        return $array;
    }

    /**
     * Sort the array using the given callback.
     *
     * @param array    $array
     * @param callable $callback
     *
     * @return array
     *
     * public static function sort($array, callable $callback)
     * {
     * return Collection::make($array)->sortBy($callback)->all();
     * }/**/

    /**
     * Filter the array using the given callback.
     *
     * @param array    $array
     * @param callable $callback
     *
     * @return array
     */
    public static function where($array, callable $callback)
    {
        return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
    }

    /**
     * Map an array recursively
     *
     * @param callable $callback
     * @param array    $array
     * @param bool     $recursive Array map recursively
     *
     * @return array
     */
    public static function map($callback, array $array, $recursive = false)
    {
        if($recursive){
            $func = function ($item) use (&$func, &$callback) {
                return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
            };

            return array_map($func, $array);
        }

        return array_map($callback, $array);
    }

    /**
     * Explode the "value" and "key" arguments passed to "pluck".
     *
     * @param string|array      $value
     * @param string|array|null $key
     *
     * @return array
     */
    public static function explodePluckParameters($value, $key)
    {
        $value = is_string($value) ? explode('.', $value) : $value;

        $key = is_null($key) || is_array($key) ? $key : explode('.', $key);

        return [$value, $key];
    }
}