<?php

namespace Reaction\Db;

use function PHPSTORM_META\elementType;
use Reaction\Base\Component;
use Reaction\Db\Expressions\Expression;
use Reaction\Events\EventEmitterWildcardInterface;
use Reaction\Promise\ExtendedPromiseInterface;
use Reaction\Promise\LazyPromiseInterface;
use function Reaction\Promise\reject;
use function Reaction\Promise\resolve;

/**
 * Class Command
 * @package Reaction\Db
 * @property DatabaseInterface $db
 * @property string            $sql
 * @property string            $rawSql
 */
class Command extends Component implements CommandInterface
{
    /**
     * @var array the parameters (name => value) that are bound to the current PDO statement.
     * This property is maintained by methods such as [[bindValue()]]. It is mainly provided for logging purpose
     * and is used to generate [[rawSql]]. Do not modify it directly.
     */
    public $params = [];
    /**
     * @var ConnectionInterface the DB connection that this command is associated with. Used for transactions
     */
    public $connection;
    /**
     * @var string Default fetch mode
     */
    public $fetchMode = self::FETCH_MODE_ASSOC;
    /**
     * @var int Query cache duration
     */
    public $queryCacheDuration;

    /**
     * @var DatabaseInterface the DB that this command is associated with
     */
    protected $_db;
    /**
     * @var string the SQL statement that this command represents
     */
    protected $_sql;
    /**
     * @var string Table name marked for schema refresh
     */
    protected $_refreshTableName;

    /**
     * Enables query cache for this command.
     * @param int $duration the number of seconds that query result of this command can remain valid in the cache.
     * If this is not set, the value of [[DatabaseInterface::queryCacheDuration]] will be used instead.
     * Use 0 to indicate that the cached data will never expire.
     * @return $this the command object itself
     */
    public function cache($duration = null)
    {
        $this->queryCacheDuration = $duration === null ? $this->db->queryCacheDuration : $duration;
        return $this;
    }

    /**
     * Get database
     * @return DatabaseInterface
     */
    public function getDb() {
        return $this->_db;
    }

    /**
     * Set database
     * @param DatabaseInterface|string|array $database
     * @throws \Reaction\Exceptions\InvalidConfigException
     */
    public function setDb($database) {
        if ($database instanceof DatabaseInterface) {
            $db = $database;
        } elseif (is_string($database) && \Reaction::$app->has($database)) {
            $db = \Reaction::$app->get($database);
        } else {
            $db = \Reaction::create($database);
        }
        $this->_db = $db;
    }

    /**
     * Set connection to use with
     * @param ConnectionInterface|TransactionInterface|null $connection
     * @return $this this command instance
     */
    public function setConnection($connection = null)
    {
        if ($connection instanceof TransactionInterface) {
            $connection = $connection->getConnection();
        }
        $this->connection = $connection;
        if ($connection instanceof EventEmitterWildcardInterface) {
            //Remove connection instance on close
            $this->connection->once(ConnectionInterface::EVENT_CLOSE, function() use ($connection) {
                if ($this->connection === $connection) {
                    $this->connection = null;
                }
            });
        }
        return $this;
    }

    /**
     * Returns the SQL statement for this command.
     * @return string the SQL statement to be executed
     */
    public function getSql()
    {
        return $this->_sql;
    }

    /**
     * Specifies the SQL statement to be executed. The SQL statement will be quoted using [[Connection::quoteSql()]].
     * The previous SQL (if any) will be discarded, and [[params]] will be cleared as well. See [[reset()]]
     * for details.
     *
     * @param string $sql the SQL statement to be set.
     * @return $this this command instance
     * @see reset()
     * @see cancel()
     */
    public function setSql($sql)
    {
        if ($sql !== $this->_sql) {
            $this->reset();
            $this->_sql = $this->db->quoteSql($sql);
        }

        return $this;
    }

    /**
     * Specifies the SQL statement to be executed. The SQL statement will not be modified in any way.
     * The previous SQL (if any) will be discarded, and [[params]] will be cleared as well. See [[reset()]]
     * for details.
     *
     * @param string $sql the SQL statement to be set.
     * @return $this this command instance
     * @see reset()
     * @see cancel()
     */
    public function setRawSql($sql)
    {
        if ($sql !== $this->_sql) {
            $this->reset();
            $this->_sql = $sql;
        }

        return $this;
    }

    /**
     * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
     * Note that the return value of this method should mainly be used for logging purpose.
     * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
     * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]].
     */
    public function getRawSql()
    {
        if (empty($this->params)) {
            return $this->_sql;
        }
        $params = [];
        foreach ($this->params as $name => $value) {
            if (is_string($name) && strncmp(':', $name, 1)) {
                $name = ':' . $name;
            }
            if (is_string($value)) {
                $params[$name] = $this->db->quoteValue($value);
            } elseif (is_bool($value)) {
                $params[$name] = ($value ? 'TRUE' : 'FALSE');
            } elseif ($value === null) {
                $params[$name] = 'NULL';
            } elseif ((!is_object($value) && !is_resource($value)) || $value instanceof Expression) {
                $params[$name] = $value;
            }
        }
        if (!isset($params[1])) {
            return strtr($this->_sql, $params);
        }
        $sql = '';
        foreach (explode('?', $this->_sql) as $i => $part) {
            $sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
        }

        return $sql;
    }

    /**
     * Binds a value to a parameter.
     * @param string|int $name Parameter identifier. For a prepared statement
     * using named placeholders, this will be a parameter name of
     * the form `:name`. For a prepared statement using question mark
     * placeholders, this will be the 1-indexed position of the parameter.
     * @param mixed $value The value to bind to the parameter
     * @return $this the current command being executed
     * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php
     */
    public function bindValue($name, $value)
    {
        $this->params[$name] = $value;

        return $this;
    }

    /**
     * Binds a list of values to the corresponding parameters.
     * This is similar to [[bindValue()]] except that it binds multiple values at a time.
     * Note that the SQL data type of each value is determined by its PHP type.
     * @param array $values the values to be bound. This must be given in terms of an associative
     * array with array keys being the parameter names, and array values the corresponding parameter values,
     * e.g. `[':name' => 'John', ':age' => 25]`.
     * @return $this the current command being executed
     */
    public function bindValues($values)
    {
        if (empty($values)) {
            return $this;
        }

        foreach ($values as $name => $value) {
            $this->params[$name] = $value;
        }

        return $this;
    }

    /**
     * Fetch results
     * @param array  $results
     * @param string $fetchMethod
     * @param string $fetchMode
     * @return array|null|object|ExtendedPromiseInterface
     * @internal
     */
    public function fetchResults($results, $fetchMethod = self::FETCH_ALL, $fetchMode = self::FETCH_MODE_ASSOC) {
        $data = [];
        if ($fetchMethod === '') {
            return new DataReader($this, $results);
        }
        if (empty($results)) {
            return in_array($fetchMethod, [static::FETCH_ALL, static::FETCH_COLUMN]) ? [] : reject(null);
        } elseif (in_array($fetchMethod, [static::FETCH_FIELD, static::FETCH_ROW])) {
            $firstRow = reset($results);
            return $this->fetchResultsRow($firstRow, $fetchMethod, $fetchMode);
        }
        foreach ($results as $row) {
            $dataRow = $this->fetchResultsRow($row, $fetchMethod, $fetchMode);
            if ($dataRow === null) {
                continue;
            }
            $data[] = $dataRow;
        }
        return $data;
    }

    /**
     * Fetch results row
     * @param array  $row
     * @param string $fetchMethod
     * @param string $fetchMode
     * @param int    $colIndex
     * @return array|null|object
     * @internal
     */
    public function fetchResultsRow($row = [], $fetchMethod = self::FETCH_ALL, $fetchMode = self::FETCH_MODE_ASSOC, $colIndex = 0) {
        if (in_array($fetchMethod, [static::FETCH_ALL, static::FETCH_ROW])) {
            $this->processResultRow($row);
            return $fetchMode === static::FETCH_MODE_OBJECT ? (object)$row : $row;
        } elseif (in_array($fetchMethod, [static::FETCH_COLUMN, static::FETCH_FIELD]) && $colIndex < count($row)) {
            $rowIndexed = array_values($row);
            $this->processResultRow($rowIndexed[$colIndex]);
            return $rowIndexed[$colIndex];
        }
        return null;
    }

    /**
     * Process result row (remove slashes etc.)
     * @param mixed $row
     */
    protected function processResultRow(&$row)
    {
        if (is_array($row)) {
            foreach ($row as &$fieldValue) {
                if (!is_string($fieldValue)) {
                    continue;
                }
                $fieldValue = stripslashes($fieldValue);
            }
        } elseif (is_string($row)) {
            $row = stripslashes($row);
        }
    }

    /**
     * Creates an INSERT command.
     *
     * For example,
     *
     * ```php
     * $connection->createCommand()->insert('user', [
     *     'name' => 'Sam',
     *     'age' => 30,
     * ])->execute();
     * ```
     *
     * The method will properly escape the column names, and bind the values to be inserted.
     *
     * Note that the created command is not executed until [[execute()]] is called.
     *
     * @param string $table the table that new rows will be inserted into.
     * @param array|\Reaction\Db\Query $columns the column data (name => value) to be inserted into the table or instance
     * of [[Reaction\Db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
     * @return $this the command object itself
     */
    public function insert($table, $columns)
    {
        $params = [];
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);

        return $this->setSql($sql)->bindValues($params);
    }

    /**
     * Creates an INSERT command with RETURNING statement.
     *
     * For example,
     *
     * ```php
     * $connection->createCommand()->insertAndReturn('user', [
     *     'name' => 'Sam',
     *     'age' => 30,
     * ])->execute();
     * ```
     *
     * The method will properly escape the column names, and bind the values to be inserted.
     *
     * Note that the created command is not executed until [[execute()]] is called.
     *
     * @param string $table the table that new rows will be inserted into.
     * @param array|\Reaction\Db\Query $columns the column data (name => value) to be inserted into the table or instance
     * of [[Reaction\Db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
     * @return $this the command object itself
     */
    public function insertAndReturn($table, $columns) {
        $params = [];
        $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
        $tableSchema = $this->db->getSchema()->getTableSchema($table);
        $returnColumns = $tableSchema->getColumnNames();
        if (!empty($returnColumns)) {
            $returning = [];
            foreach ((array)$returnColumns as $name) {
                $returning[] = $this->db->quoteColumnName($name);
            }
            $sql .= ' RETURNING ' . implode(', ', $returning);
        }

        return $this->setSql($sql)->bindValues($params);
    }

    /**
     * Creates a batch INSERT command.
     *
     * For example,
     *
     * ```php
     * $connection->createCommand()->batchInsert('user', ['name', 'age'], [
     *     ['Tom', 30],
     *     ['Jane', 20],
     *     ['Linda', 25],
     * ])->execute();
     * ```
     *
     * The method will properly escape the column names, and quote the values to be inserted.
     *
     * Note that the values in each row must match the corresponding column names.
     *
     * Also note that the created command is not executed until [[execute()]] is called.
     *
     * @param string $table the table that new rows will be inserted into.
     * @param array $columns the column names
     * @param array|\Generator $rows the rows to be batch inserted into the table
     * @return $this the command object itself
     */
    public function batchInsert($table, $columns, $rows)
    {
        $table = $this->db->quoteSql($table);
        $columns = array_map(function($column) {
            return $this->db->quoteSql($column);
        }, $columns);

        $params = [];
        $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows, $params);

        $this->setRawSql($sql);
        $this->bindValues($params);

        return $this;
    }

    /**
     * Creates a command to insert rows into a database table if
     * they do not already exist (matching unique constraints),
     * or update them if they do.
     *
     * For example,
     *
     * ```php
     * $sql = $queryBuilder->upsert('pages', [
     *     'name' => 'Front page',
     *     'url' => 'http://example.com/', // url is unique
     *     'visits' => 0,
     * ], [
     *     'visits' => new \Reaction\Db\Expressions\Expression('visits + 1'),
     * ], $params);
     * ```
     *
     * The method will properly escape the table and column names.
     *
     * @param string $table the table that new rows will be inserted into/updated in.
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
     * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
     * If `true` is passed, the column data will be updated to match the insert column data.
     * If `false` is passed, no update will be performed if the column data already exists.
     * @param array $params the parameters to be bound to the command.
     * @return $this the command object itself.
     */
    public function upsert($table, $insertColumns, $updateColumns = true, $params = [])
    {
        $sql = $this->db->getQueryBuilder()->upsert($table, $insertColumns, $updateColumns, $params);

        return $this->setSql($sql)->bindValues($params);
    }

    /**
     * Creates an UPDATE command.
     *
     * For example,
     *
     * ```php
     * $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();
     * ```
     *
     * or with using parameter binding for the condition:
     *
     * ```php
     * $minAge = 30;
     * $connection->createCommand()->update('user', ['status' => 1], 'age > :minAge', [':minAge' => $minAge])->execute();
     * ```
     *
     * The method will properly escape the column names and bind the values to be updated.
     *
     * Note that the created command is not executed until [[execute()]] is called.
     *
     * @param string $table the table to be updated.
     * @param array $columns the column data (name => value) to be updated.
     * @param string|array $condition the condition that will be put in the WHERE part. Please
     * refer to [[Query::where()]] on how to specify condition.
     * @param array $params the parameters to be bound to the command
     * @return $this the command object itself
     */
    public function update($table, $columns, $condition = '', $params = [])
    {
        $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);

        return $this->setSql($sql)->bindValues($params);
    }

    /**
     * Creates a DELETE command.
     *
     * For example,
     *
     * ```php
     * $connection->createCommand()->delete('user', 'status = 0')->execute();
     * ```
     *
     * or with using parameter binding for the condition:
     *
     * ```php
     * $status = 0;
     * $connection->createCommand()->delete('user', 'status = :status', [':status' => $status])->execute();
     * ```
     *
     * The method will properly escape the table and column names.
     *
     * Note that the created command is not executed until [[execute()]] is called.
     *
     * @param string $table the table where the data will be deleted from.
     * @param string|array $condition the condition that will be put in the WHERE part. Please
     * refer to [[Query::where()]] on how to specify condition.
     * @param array $params the parameters to be bound to the command
     * @return $this the command object itself
     */
    public function delete($table, $condition = '', $params = [])
    {
        $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);

        return $this->setSql($sql)->bindValues($params);
    }

    /**
     * Creates a SQL command for creating a new DB table.
     *
     * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
     * where name stands for a column name which will be properly quoted by the method, and definition
     * stands for the column type which can contain an abstract DB type.
     * The method [[QueryBuilder::getColumnType()]] will be called
     * to convert the abstract column types to physical ones. For example, `string` will be converted
     * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
     *
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
     * inserted into the generated SQL.
     *
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
     * @param array $columns the columns (name => definition) in the new table.
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
     * @return $this the command object itself
     */
    public function createTable($table, $columns, $options = null)
    {
        $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for renaming a DB table.
     * @param string $table the table to be renamed. The name will be properly quoted by the method.
     * @param string $newName the new table name. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function renameTable($table, $newName)
    {
        $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping a DB table.
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function dropTable($table)
    {
        $sql = $this->db->getQueryBuilder()->dropTable($table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for truncating a DB table.
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function truncateTable($table)
    {
        $sql = $this->db->getQueryBuilder()->truncateTable($table);

        return $this->setSql($sql);
    }

    /**
     * Creates a SQL command for adding a new DB column.
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
     * @param string $column the name of the new column. The name will be properly quoted by the method.
     * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
     * to convert the give column type to the physical one. For example, `string` will be converted
     * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
     * @return $this the command object itself
     */
    public function addColumn($table, $column, $type)
    {
        $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping a DB column.
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function dropColumn($table, $column)
    {
        $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for renaming a column.
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function renameColumn($table, $oldName, $newName)
    {
        $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for changing the definition of a column.
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
     * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
     * to convert the give column type to the physical one. For example, `string` will be converted
     * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
     * @return $this the command object itself
     */
    public function alterColumn($table, $column, $type)
    {
        $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for adding a primary key constraint to an existing table.
     * The method will properly quote the table and column names.
     * @param string $name the name of the primary key constraint.
     * @param string $table the table that the primary key constraint will be added to.
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
     * @return $this the command object itself.
     */
    public function addPrimaryKey($name, $table, $columns)
    {
        $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for removing a primary key constraint to an existing table.
     * @param string $name the name of the primary key constraint to be removed.
     * @param string $table the table that the primary key constraint will be removed from.
     * @return $this the command object itself
     */
    public function dropPrimaryKey($name, $table)
    {
        $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for adding a foreign key constraint to an existing table.
     * The method will properly quote the table and column names.
     * @param string $name the name of the foreign key constraint.
     * @param string $table the table that the foreign key constraint will be added to.
     * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
     * @param string $refTable the table that the foreign key references to.
     * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
     * @return $this the command object itself
     */
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
    {
        $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping a foreign key constraint.
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function dropForeignKey($name, $table)
    {
        $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for creating a new index.
     * @param string $name the name of the index. The name will be properly quoted by the method.
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
     * by commas. The column names will be properly quoted by the method.
     * @param bool $unique whether to add UNIQUE constraint on the created index.
     * @return $this the command object itself
     */
    public function createIndex($name, $table, $columns, $unique = false)
    {
        $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping an index.
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function dropIndex($name, $table)
    {
        $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for adding an unique constraint to an existing table.
     * @param string $name the name of the unique constraint.
     * The name will be properly quoted by the method.
     * @param string $table the table that the unique constraint will be added to.
     * The name will be properly quoted by the method.
     * @param string|array $columns the name of the column to that the constraint will be added on.
     * If there are multiple columns, separate them with commas.
     * The name will be properly quoted by the method.
     * @return $this the command object itself.
     */
    public function addUnique($name, $table, $columns)
    {
        $sql = $this->db->getQueryBuilder()->addUnique($name, $table, $columns);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping an unique constraint.
     * @param string $name the name of the unique constraint to be dropped.
     * The name will be properly quoted by the method.
     * @param string $table the table whose unique constraint is to be dropped.
     * The name will be properly quoted by the method.
     * @return $this the command object itself.
     */
    public function dropUnique($name, $table)
    {
        $sql = $this->db->getQueryBuilder()->dropUnique($name, $table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for adding a check constraint to an existing table.
     * @param string $name the name of the check constraint.
     * The name will be properly quoted by the method.
     * @param string $table the table that the check constraint will be added to.
     * The name will be properly quoted by the method.
     * @param string $expression the SQL of the `CHECK` constraint.
     * @return $this the command object itself.
     */
    public function addCheck($name, $table, $expression)
    {
        $sql = $this->db->getQueryBuilder()->addCheck($name, $table, $expression);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping a check constraint.
     * @param string $name the name of the check constraint to be dropped.
     * The name will be properly quoted by the method.
     * @param string $table the table whose check constraint is to be dropped.
     * The name will be properly quoted by the method.
     * @return $this the command object itself.
     */
    public function dropCheck($name, $table)
    {
        $sql = $this->db->getQueryBuilder()->dropCheck($name, $table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for adding a default value constraint to an existing table.
     * @param string $name the name of the default value constraint.
     * The name will be properly quoted by the method.
     * @param string $table the table that the default value constraint will be added to.
     * The name will be properly quoted by the method.
     * @param string $column the name of the column to that the constraint will be added on.
     * The name will be properly quoted by the method.
     * @param mixed $value default value.
     * @return $this the command object itself.
     */
    public function addDefaultValue($name, $table, $column, $value)
    {
        $sql = $this->db->getQueryBuilder()->addDefaultValue($name, $table, $column, $value);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for dropping a default value constraint.
     * @param string $name the name of the default value constraint to be dropped.
     * The name will be properly quoted by the method.
     * @param string $table the table whose default value constraint is to be dropped.
     * The name will be properly quoted by the method.
     * @return $this the command object itself.
     */
    public function dropDefaultValue($name, $table)
    {
        $sql = $this->db->getQueryBuilder()->dropDefaultValue($name, $table);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Creates a SQL command for resetting the sequence value of a table's primary key.
     * The sequence will be reset such that the primary key of the next new row inserted
     * will have the specified value or 1.
     * @param string $table the name of the table whose primary key sequence will be reset
     * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
     * the next new row's primary key will have a value 1.
     * @return $this the command object itself
     */
    public function resetSequence($table, $value = null)
    {
        $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);

        return $this->setSql($sql);
    }

    /**
     * Builds a SQL command for enabling or disabling integrity check.
     * @param bool $check whether to turn on or off the integrity check.
     * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current
     * or default schema.
     * @param string $table the table name.
     * @return $this the command object itself
     */
    public function checkIntegrity($check = true, $schema = '', $table = '')
    {
        $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table);

        return $this->setSql($sql);
    }

    /**
     * Builds a SQL command for adding comment to column.
     *
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function addCommentOnColumn($table, $column, $comment)
    {
        $sql = $this->db->getQueryBuilder()->addCommentOnColumn($table, $column, $comment);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Builds a SQL command for adding comment to table.
     *
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function addCommentOnTable($table, $comment)
    {
        $sql = $this->db->getQueryBuilder()->addCommentOnTable($table, $comment);

        return $this->setSql($sql);
    }

    /**
     * Builds a SQL command for dropping comment from column.
     *
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function dropCommentFromColumn($table, $column)
    {
        $sql = $this->db->getQueryBuilder()->dropCommentFromColumn($table, $column);

        return $this->setSql($sql)->requireTableSchemaRefresh($table);
    }

    /**
     * Builds a SQL command for dropping comment from table.
     *
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
     * @return $this the command object itself
     */
    public function dropCommentFromTable($table)
    {
        $sql = $this->db->getQueryBuilder()->dropCommentFromTable($table);

        return $this->setSql($sql);
    }

    /**
     * Creates a SQL View.
     *
     * @param string $viewName the name of the view to be created.
     * @param string|Query $subquery the select statement which defines the view.
     * This can be either a string or a [[Query]] object.
     * @return $this the command object itself.
     */
    public function createView($viewName, $subquery)
    {
        $sql = $this->db->getQueryBuilder()->createView($viewName, $subquery);

        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
    }

    /**
     * Drops a SQL View.
     *
     * @param string $viewName the name of the view to be dropped.
     * @return $this the command object itself.
     */
    public function dropView($viewName)
    {
        $sql = $this->db->getQueryBuilder()->dropView($viewName);

        return $this->setSql($sql)->requireTableSchemaRefresh($viewName);
    }

    /**
     * Executes the SQL statement.
     * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
     * No result set will be returned.
     * @param bool $lazy Use LazyPromise
     * @return ExtendedPromiseInterface|LazyPromiseInterface
     */
    public function execute($lazy = true)
    {
        $sql = $this->getSql();
        $rawSql = $this->getRawSql();

        if ($sql == '') {
            return reject(false);
        }

        $execPromise = $this->internalExecute($rawSql, [], $lazy);
        $needResCount = $this->needResultsCount();
        $thenCallback = function($results = []) use ($needResCount) {
            $result = $needResCount ? count($results) : true;
            return $this->refreshTableSchema()->then(
                function() use ($result) {
                    return $result;
                }
            );
        };
        return $execPromise instanceof LazyPromiseInterface
            ? $execPromise->thenLazy($thenCallback)
            : $execPromise->then($thenCallback);
    }

    /**
     * Logs the current database query if query logging is enabled and returns
     * the profiling token if profiling is enabled.
     * @param string $category the log category.
     * @return array array of two elements, the first is boolean of whether profiling is enabled or not.
     * The second is the rawSql if it has been created.
     */
    protected function logQuery($category)
    {
        if ($this->db->enableLogging) {
            $rawSql = $this->getRawSql();
            $message = sprintf("SQL: \"%s\"\nCategory: %s", $rawSql, $category);
            \Reaction::info($message);
        }
        if (!$this->db->enableProfiling) {
            return [false, isset($rawSql) ? $rawSql : null];
        }

        return [true, isset($rawSql) ? $rawSql : $this->getRawSql()];
    }

    /**
     * Marks a specified table schema to be refreshed after command execution.
     * @param string $name name of the table, which schema should be refreshed.
     * @return $this this command instance
     */
    protected function requireTableSchemaRefresh($name)
    {
        $this->_refreshTableName = $name;
        return $this;
    }

    /**
     * Refreshes table schema, which was marked by [[requireTableSchemaRefresh()]].
     * @return ExtendedPromiseInterface to know when finished
     */
    protected function refreshTableSchema()
    {
        if ($this->_refreshTableName !== null) {
            return $this->db->getSchema()->refreshTableMetadata($this->_refreshTableName);
        }
        return resolve(true);
    }

    /**
     * Executes the SQL statement and returns query result.
     * This method is for executing a SQL query that returns result set, such as `SELECT`.
     * @return LazyPromiseInterface with DataReader the reader object for fetching the query result
     */
    public function query()
    {
        return $this->queryInternal('');
    }

    /**
     * Executes the SQL statement and returns ALL rows at once.
     * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
     * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
     * @return LazyPromiseInterface with all rows of the query result. Each array element is an array representing a row of data.
     * An empty array is returned if the query results in nothing.
     */
    public function queryAll($fetchMode = null)
    {
        return $this->queryInternal(static::FETCH_ALL, $fetchMode);
    }

    /**
     * Executes the SQL statement and returns the first row of the result.
     * This method is best used when only the first row of result is needed for a query.
     * @param int $fetchMode the result fetch mode. Please refer to [PHP manual](http://php.net/manual/en/pdostatement.setfetchmode.php)
     * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
     * @return LazyPromiseInterface with the first row (in terms of an array) of the query result. False is returned if the query
     * results in nothing.
     */
    public function queryOne($fetchMode = null)
    {
        return $this->queryInternal(static::FETCH_ROW, $fetchMode);
    }

    /**
     * Executes the SQL statement and returns the value of the first column in the first row of data.
     * This method is best used when only a single value is needed for a query.
     * @return LazyPromiseInterface the value of the first column in the first row of the query result.
     * False is returned if there is no value.
     */
    public function queryScalar()
    {
        return $this->queryInternal(static::FETCH_FIELD)->thenLazy(
            function($result) {
                if (is_resource($result) && get_resource_type($result) === 'stream') {
                    return stream_get_contents($result);
                }
                return $result;
            }
        );
    }

    /**
     * Executes the SQL statement and returns the first column of the result.
     * This method is best used when only the first column of result (i.e. the first element in each row)
     * is needed for a query.
     * @return LazyPromiseInterface with the first column of the query result. Empty array is returned if the query results in nothing.
     */
    public function queryColumn()
    {
        return $this->queryInternal(static::FETCH_COLUMN);
    }

    /**
     * Performs the actual DB query of a SQL statement.
     * @param string $fetchMethod method of PDOStatement to be called
     * @param int    $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
     * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
     * @param bool   $lazy Use LazyPromise
     * @return ExtendedPromiseInterface|LazyPromiseInterface with the method execution result
     */
    protected function queryInternal($fetchMethod, $fetchMode = null, $lazy = true)
    {
        $fetchMode = isset($fetchMode) ? $fetchMode : $this->fetchMode;
        list($profile, $rawSql) = $this->logQuery(__METHOD__);

        $self = $this;
        $execPromise = $this->internalExecute($this->sql, $this->params, $lazy);
        if ($execPromise instanceof LazyPromiseInterface) {
            return $execPromise->thenLazy(
                function($results) use ($fetchMethod, $fetchMode) {
                    return $this->fetchResults($results, $fetchMethod, $fetchMode);
                }
            );
        } else {
            $profileId = $profile ? \Reaction::$app->logger->profile('Query: ' . $rawSql) : null;
            return $execPromise->then(
                function($results) use ($self, $fetchMethod, $fetchMode) {
                    return $self->fetchResults($results, $fetchMethod, $fetchMode);
                }
            )->then(
                function($data) use ($profileId) {
                    \Reaction::profileEnd($profileId);
                    return $data;
                },
                function($error = null) use ($profileId) {
                    \Reaction::profileEnd($profileId);
                    return reject($error);
                }
            );
        }
    }

    /**
     * Check that query must return affected rows count
     * @return bool
     */
    protected function needResultsCount() {
        $pattern = '/^\s*(DELETE|UPDATE)\b/i';
        return $this->checkQueryByPattern($pattern);
    }

    /**
     * Check that query is SELECT|SHOW|DESCRIBE type
     * @return bool
     */
    protected function isReadQuery() {
        $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
        return $this->checkQueryByPattern($pattern);
    }

    /**
     * Check that query is INSERT type
     * @return bool
     */
    protected function isInsertQuery() {
        $pattern = '/^\s*(INSERT)\b/i';
        return $this->checkQueryByPattern($pattern);
    }

    /**
     * Check that query is UPDATE type
     * @return bool
     */
    protected function isUpdateQuery() {
        $pattern = '/^\s*(UPDATE)\b/i';
        return $this->checkQueryByPattern($pattern);
    }

    /**
     * Check that query is DELETE type
     * @return bool
     */
    protected function isDeleteQuery() {
        $pattern = '/^\s*(DELETE)\b/i';
        return $this->checkQueryByPattern($pattern);
    }

    /**
     * Check query string by pattern
     * @param string      $pattern
     * @param string|null $sql
     * @return bool
     * @internal
     */
    protected function checkQueryByPattern($pattern, $sql = null) {
        if (!isset($sql)) {
            $sql = $this->getSql();
        }
        if ($sql === "") {
            return false;
        }
        return preg_match($pattern, $sql) > 0;
    }

    /**
     * Execute SQL statement internally
     * @param string $sql
     * @param array  $params
     * @param bool   $lazy
     * @return ExtendedPromiseInterface
     */
    protected function internalExecute($sql, $params = [], $lazy = true)
    {
        return isset($this->connection)
            ? $this->connection->executeSql($sql, $params, $lazy)
            : $this->db->executeSql($sql, $params, $lazy);
    }

    /**
     * Resets command properties to their initial state.
     */
    protected function reset()
    {
        $this->_sql = null;
        $this->params = [];
        $this->_refreshTableName = null;
        //$this->_isolationLevel = false;
    }
}


<?php namespace Vaccuum\Dispatcher\Traits;

use Closure;
use ReflectionFunction;
use Vaccuum\Contracts\Dispatcher\DispatcherException;

trait TClosureInvocation
{
    /**
     * Invoke closure using arguments.
     *
     * @param array   $arguments
     * @param Closure $action
     *
     * @throws DispatcherException
     * @return mixed
     */
    protected function invokeClosure(array $arguments, Closure $action)
    {
        $reflection = new ReflectionFunction($action);
        $parameters = [];

        foreach ($reflection->getParameters() as $parameter)
        {
            if (isset($arguments[$parameter->name]))
            {
                $parameters[] = $arguments[$parameter->name];
            }
            elseif ($parameter->isDefaultValueAvailable())
            {
                $parameters[] = $parameter->getDefaultValue();
            }
            else
            {
                $message = "{$parameter->name} argument cannot be resolved.";
                throw new DispatcherException($message, $arguments, $action);
            }
        }

        return $reflection->invokeArgs($parameters);
    }
}


<?php
/**
 * PEAR_Command_Config (config-show, config-get, config-set, config-help, config-create commands)
 *
 * PHP versions 4 and 5
 *
 * @category   pear
 * @package    PEAR
 * @author     Stig Bakken <ssb@php.net>
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    CVS: $Id: Config.php 313024 2011-07-06 19:51:24Z dufuz $
 * @link       http://pear.php.net/package/PEAR
 * @since      File available since Release 0.1
 */

/**
 * base class
 */
require_once 'PEAR/Command/Common.php';

/**
 * PEAR commands for managing configuration data.
 *
 * @category   pear
 * @package    PEAR
 * @author     Stig Bakken <ssb@php.net>
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: 1.9.4
 * @link       http://pear.php.net/package/PEAR
 * @since      Class available since Release 0.1
 */
class PEAR_Command_Config extends PEAR_Command_Common
{
    var $commands = array(
        'config-show' => array(
            'summary' => 'Show All Settings',
            'function' => 'doConfigShow',
            'shortcut' => 'csh',
            'options' => array(
                'channel' => array(
                    'shortopt' => 'c',
                    'doc' => 'show configuration variables for another channel',
                    'arg' => 'CHAN',
                    ),
),
            'doc' => '[layer]
Displays all configuration values.  An optional argument
may be used to tell which configuration layer to display.  Valid
configuration layers are "user", "system" and "default". To display
configurations for different channels, set the default_channel
configuration variable and run config-show again.
',
            ),
        'config-get' => array(
            'summary' => 'Show One Setting',
            'function' => 'doConfigGet',
            'shortcut' => 'cg',
            'options' => array(
                'channel' => array(
                    'shortopt' => 'c',
                    'doc' => 'show configuration variables for another channel',
                    'arg' => 'CHAN',
                    ),
),
            'doc' => '<parameter> [layer]
Displays the value of one configuration parameter.  The
first argument is the name of the parameter, an optional second argument
may be used to tell which configuration layer to look in.  Valid configuration
layers are "user", "system" and "default".  If no layer is specified, a value
will be picked from the first layer that defines the parameter, in the order
just specified.  The configuration value will be retrieved for the channel
specified by the default_channel configuration variable.
',
            ),
        'config-set' => array(
            'summary' => 'Change Setting',
            'function' => 'doConfigSet',
            'shortcut' => 'cs',
            'options' => array(
                'channel' => array(
                    'shortopt' => 'c',
                    'doc' => 'show configuration variables for another channel',
                    'arg' => 'CHAN',
                    ),
),
            'doc' => '<parameter> <value> [layer]
Sets the value of one configuration parameter.  The first argument is
the name of the parameter, the second argument is the new value.  Some
parameters are subject to validation, and the command will fail with
an error message if the new value does not make sense.  An optional
third argument may be used to specify in which layer to set the
configuration parameter.  The default layer is "user".  The
configuration value will be set for the current channel, which
is controlled by the default_channel configuration variable.
',
            ),
        'config-help' => array(
            'summary' => 'Show Information About Setting',
            'function' => 'doConfigHelp',
            'shortcut' => 'ch',
            'options' => array(),
            'doc' => '[parameter]
Displays help for a configuration parameter.  Without arguments it
displays help for all configuration parameters.
',
           ),
        'config-create' => array(
            'summary' => 'Create a Default configuration file',
            'function' => 'doConfigCreate',
            'shortcut' => 'coc',
            'options' => array(
                'windows' => array(
                    'shortopt' => 'w',
                    'doc' => 'create a config file for a windows install',
                    ),
            ),
            'doc' => '<root path> <filename>
Create a default configuration file with all directory configuration
variables set to subdirectories of <root path>, and save it as <filename>.
This is useful especially for creating a configuration file for a remote
PEAR installation (using the --remoteconfig option of install, upgrade,
and uninstall).
',
            ),
        );

    /**
     * PEAR_Command_Config constructor.
     *
     * @access public
     */
    function PEAR_Command_Config(&$ui, &$config)
    {
        parent::PEAR_Command_Common($ui, $config);
    }

    function doConfigShow($command, $options, $params)
    {
        $layer = null;
        if (is_array($params)) {
            $layer = isset($params[0]) ? $params[0] : null;
        }

        // $params[0] -> the layer
        if ($error = $this->_checkLayer($layer)) {
            return $this->raiseError("config-show:$error");
        }

        $keys = $this->config->getKeys();
        sort($keys);
        $channel = isset($options['channel']) ? $options['channel'] :
            $this->config->get('default_channel');
        $reg = &$this->config->getRegistry();
        if (!$reg->channelExists($channel)) {
            return $this->raiseError('Channel "' . $channel . '" does not exist');
        }

        $channel = $reg->channelName($channel);
        $data = array('caption' => 'Configuration (channel ' . $channel . '):');
        foreach ($keys as $key) {
            $type = $this->config->getType($key);
            $value = $this->config->get($key, $layer, $channel);
            if ($type == 'password' && $value) {
                $value = '********';
            }

            if ($value === false) {
                $value = 'false';
            } elseif ($value === true) {
                $value = 'true';
            }

            $data['data'][$this->config->getGroup($key)][] = array($this->config->getPrompt($key) , $key, $value);
        }

        foreach ($this->config->getLayers() as $layer) {
            $data['data']['Config Files'][] = array(ucfirst($layer) . ' Configuration File', 'Filename' , $this->config->getConfFile($layer));
        }

        $this->ui->outputData($data, $command);
        return true;
    }

    function doConfigGet($command, $options, $params)
    {
        $args_cnt = is_array($params) ? count($params) : 0;
        switch ($args_cnt) {
            case 1:
                $config_key = $params[0];
                $layer = null;
                break;
            case 2:
                $config_key = $params[0];
                $layer = $params[1];
                if ($error = $this->_checkLayer($layer)) {
                    return $this->raiseError("config-get:$error");
                }
                break;
            case 0:
            default:
                return $this->raiseError("config-get expects 1 or 2 parameters");
        }

        $reg = &$this->config->getRegistry();
        $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
        if (!$reg->channelExists($channel)) {
            return $this->raiseError('Channel "' . $channel . '" does not exist');
        }

        $channel = $reg->channelName($channel);
        $this->ui->outputData($this->config->get($config_key, $layer, $channel), $command);
        return true;
    }

    function doConfigSet($command, $options, $params)
    {
        // $param[0] -> a parameter to set
        // $param[1] -> the value for the parameter
        // $param[2] -> the layer
        $failmsg = '';
        if (count($params) < 2 || count($params) > 3) {
            $failmsg .= "config-set expects 2 or 3 parameters";
            return PEAR::raiseError($failmsg);
        }

        if (isset($params[2]) && ($error = $this->_checkLayer($params[2]))) {
            $failmsg .= $error;
            return PEAR::raiseError("config-set:$failmsg");
        }

        $channel = isset($options['channel']) ? $options['channel'] : $this->config->get('default_channel');
        $reg = &$this->config->getRegistry();
        if (!$reg->channelExists($channel)) {
            return $this->raiseError('Channel "' . $channel . '" does not exist');
        }

        $channel = $reg->channelName($channel);
        if ($params[0] == 'default_channel' && !$reg->channelExists($params[1])) {
            return $this->raiseError('Channel "' . $params[1] . '" does not exist');
        }

        if ($params[0] == 'preferred_mirror'
            && (
                !$reg->mirrorExists($channel, $params[1]) &&
                (!$reg->channelExists($params[1]) || $channel != $params[1])
            )
        ) {
            $msg  = 'Channel Mirror "' . $params[1] . '" does not exist';
            $msg .= ' in your registry for channel "' . $channel . '".';
            $msg .= "\n" . 'Attempt to run "pear channel-update ' . $channel .'"';
            $msg .= ' if you believe this mirror should exist as you may';
            $msg .= ' have outdated channel information.';
            return $this->raiseError($msg);
        }

        if (count($params) == 2) {
            array_push($params, 'user');
            $layer = 'user';
        } else {
            $layer = $params[2];
        }

        array_push($params, $channel);
        if (!call_user_func_array(array(&$this->config, 'set'), $params)) {
            array_pop($params);
            $failmsg = "config-set (" . implode(", ", $params) . ") failed, channel $channel";
        } else {
            $this->config->store($layer);
        }

        if ($failmsg) {
            return $this->raiseError($failmsg);
        }

        $this->ui->outputData('config-set succeeded', $command);
        return true;
    }

    function doConfigHelp($command, $options, $params)
    {
        if (empty($params)) {
            $params = $this->config->getKeys();
        }

        $data['caption']  = "Config help" . ((count($params) == 1) ? " for $params[0]" : '');
        $data['headline'] = array('Name', 'Type', 'Description');
        $data['border']   = true;
        foreach ($params as $name) {
            $type = $this->config->getType($name);
            $docs = $this->config->getDocs($name);
            if ($type == 'set') {
                $docs = rtrim($docs) . "\nValid set: " .
                    implode(' ', $this->config->getSetValues($name));
            }

            $data['data'][] = array($name, $type, $docs);
        }

        $this->ui->outputData($data, $command);
    }

    function doConfigCreate($command, $options, $params)
    {
        if (count($params) != 2) {
            return PEAR::raiseError('config-create: must have 2 parameters, root path and ' .
                'filename to save as');
        }

        $root = $params[0];
        // Clean up the DIRECTORY_SEPARATOR mess
        $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
        $root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"),
                             array('/', '/', '/'),
                            $root);
        if ($root{0} != '/') {
            if (!isset($options['windows'])) {
                return PEAR::raiseError('Root directory must be an absolute path beginning ' .
                    'with "/", was: "' . $root . '"');
            }

            if (!preg_match('/^[A-Za-z]:/', $root)) {
                return PEAR::raiseError('Root directory must be an absolute path beginning ' .
                    'with "\\" or "C:\\", was: "' . $root . '"');
            }
        }

        $windows = isset($options['windows']);
        if ($windows) {
            $root = str_replace('/', '\\', $root);
        }

        if (!file_exists($params[1]) && !@touch($params[1])) {
            return PEAR::raiseError('Could not create "' . $params[1] . '"');
        }

        $params[1] = realpath($params[1]);
        $config = &new PEAR_Config($params[1], '#no#system#config#', false, false);
        if ($root{strlen($root) - 1} == '/') {
            $root = substr($root, 0, strlen($root) - 1);
        }

        $config->noRegistry();
        $config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user');
        $config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data");
        $config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www");
        $config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg");
        $config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext");
        $config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs");
        $config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests");
        $config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache");
        $config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download");
        $config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp");
        $config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear");
        $config->writeConfigFile();
        $this->_showConfig($config);
        $this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"',
            $command);
    }

    function _showConfig(&$config)
    {
        $params = array('user');
        $keys = $config->getKeys();
        sort($keys);
        $channel = 'pear.php.net';
        $data = array('caption' => 'Configuration (channel ' . $channel . '):');
        foreach ($keys as $key) {
            $type = $config->getType($key);
            $value = $config->get($key, 'user', $channel);
            if ($type == 'password' && $value) {
                $value = '********';
            }

            if ($value === false) {
                $value = 'false';
            } elseif ($value === true) {
                $value = 'true';
            }
            $data['data'][$config->getGroup($key)][] =
                array($config->getPrompt($key) , $key, $value);
        }

        foreach ($config->getLayers() as $layer) {
            $data['data']['Config Files'][] =
                array(ucfirst($layer) . ' Configuration File', 'Filename' ,
                    $config->getConfFile($layer));
        }

        $this->ui->outputData($data, 'config-show');
        return true;
    }

    /**
     * Checks if a layer is defined or not
     *
     * @param string $layer The layer to search for
     * @return mixed False on no error or the error message
     */
    function _checkLayer($layer = null)
    {
        if (!empty($layer) && $layer != 'default') {
            $layers = $this->config->getLayers();
            if (!in_array($layer, $layers)) {
                return " only the layers: \"" . implode('" or "', $layers) . "\" are supported";
            }
        }

        return false;
    }
}


<?php

declare(strict_types=1);

namespace Ruwork\ObjectStore\Factory;

use Ruwork\ObjectStore\Configurator\StoreBuilder;
use Ruwork\ObjectStore\StoreInterface;
use Ruwork\ObjectStore\TypeResolver\StoreTypeResolverInterface;

final class StoreFactory implements StoreFactoryInterface
{
    private $typeResolver;

    public function __construct(StoreTypeResolverInterface $typeResolver)
    {
        $this->typeResolver = $typeResolver;
    }

    /**
     * {@inheritdoc}
     */
    public function create(string $type, array $options = []): StoreInterface
    {
        $resolvedType = $this->typeResolver->resolve($type);
        $options = $resolvedType->getOptionsResolver()->resolve($options);
        $builder = new StoreBuilder();
        $resolvedType->configureStore($builder, $options);

        return $builder->build();
    }
}


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

namespace CarteBlanche\App;

use \CarteBlanche\CarteBlanche;
use \CarteBlanche\App\Container;
use \CarteBlanche\Exception\NotFoundException;
use \CarteBlanche\Exception\RuntimeException;
use \CarteBlanche\Interfaces\FrontControllerInterface;
use \CarteBlanche\Library\Helper;
use \Patterns\Abstracts\AbstractSingleton;
use \Library\Helper\Html as HtmlHelper;
use \Library\Helper\Directory as DirectoryHelper;
use \Library\Helper\Code as CodeHelper;

/**
 * @author  Piero Wbmstr <me@e-piwi.fr>
 */
class FrontController
    extends AbstractSingleton
    implements FrontControllerInterface
{

    /**
     * @var object The current controller, must inherit `\CarteBlanche\Abstracts\AbstractController`
     */
    protected $controller;

    /**
     * @var string The current controller requested
     */
    protected $controller_name;

    /**
     * @var string The current action requested in the controller
     */
    protected $action_name;

    /**
     */
    protected function init()
    {
        $this->prepareDom();
    }

// ---------------------------------
// Getters / Setters
// ---------------------------------

    /**
     * Set the current controller
     *
     * @param   \CarteBlanche\Interfaces\ControllerInterface    $ctrl
     * @return  self
     */
    public function setController(\CarteBlanche\Interfaces\ControllerInterface $ctrl)
    {
        $this->controller = $ctrl;
        return $this;
    }

    /**
     * Get the current controller
     *
     * @return  \CarteBlanche\Interfaces\ControllerInterface
     */
    public function getController()
    {
        return $this->controller;
    }

    /**
     * Set the current controller name
     *
     * @param string $name
     * @return self
     */
    public function setControllerName($name)
    {
        $this->controller_name = $name;
        return $this;
    }

    /**
     * Get the current controller name
     *
     * @return string
     */
    public function getControllerName()
    {
        return $this->controller_name;
    }

    /**
     * Set the current action requested by the router
     *
     * @param string $type
     * @return self
     */
    public function setActionName($type)
    {
        $this->action_name = $type;
        return $this;
    }

    /**
     * Get the current action requested by the router
     *
     * @return string
     */
    public function getActionName()
    {
        return $this->action_name;
    }

// -----------------------
// Request/Response Process
// -----------------------

    /**
     * Prepare the required DOM IDs used by the global template
     *
     * @return self
     */
    protected function prepareDom()
    {
        $dom_refs = array(
            'page_menu', 'page_content', 'page_header', 'page_footer', 'page_title'
        );
        foreach($dom_refs as $_ref) {
            HtmlHelper::getNewId($_ref, true);
        }
        return $this;
    }

    /**
     * Distribute the current request route
     *
     * @return void
     */
    public function distribute()
    {
        $this
            ->_processSessionValues()
            ->_processQueryArguments()
            ;

        // if kernerl has booting errors, treat them first
        if (CarteBlanche::getContainer()->get('kernel')->hasBootErrors()) {
            $routing = array(CarteBlanche::getContainer()->get('kernel')->getBootErrors());
            if (CarteBlanche::getContainer()->get('request')->isCli()) {
                $routing['controller'] = CarteBlanche::getConfig('routing.cli.default_controller');
                $routing['action'] = CarteBlanche::getConfig('routing.cli.booterrors_action');
            } elseif (CarteBlanche::getContainer()->get('request')->isAjax()) {
                $routing['controller'] = CarteBlanche::getConfig('routing.ajax.default_controller');
                $routing['action'] = CarteBlanche::getConfig('routing.ajax.booterrors_action');
            } else {
                $routing['controller'] = CarteBlanche::getConfig('routing.mvc.default_controller');
                $routing['action'] = CarteBlanche::getConfig('routing.mvc.booterrors_action');
            }
        } else {
            $routing = CarteBlanche::getContainer()->get('router')
                ->distribute()
                ->getRouteParsed();

            // controller
            if (empty($routing['controller'])) {
                if (CarteBlanche::getContainer()->get('request')->isCli()) {
                    $routing['controller'] = CarteBlanche::getConfig('routing.cli.default_controller');
                } elseif (CarteBlanche::getContainer()->get('request')->isAjax()) {
                    $routing['controller'] = CarteBlanche::getConfig('routing.ajax.default_controller');
                } else {
                    $routing['controller'] = CarteBlanche::getConfig('routing.mvc.default_controller');
                }
            }

            // action
            if (empty($routing['action'])) {
                if (CarteBlanche::getContainer()->get('request')->isCli()) {
                    $routing['action'] = CarteBlanche::getConfig('routing.cli.default_action');
                } elseif (CarteBlanche::getContainer()->get('request')->isAjax()) {
                    $routing['action'] = CarteBlanche::getConfig('routing.ajax.default_action');
                } else {
                    $routing['action'] = CarteBlanche::getConfig('routing.mvc.default_action');
                }
            }
        }

        // arguments
        $primary_args = $routing;
        unset($primary_args['all']);
        $args = isset($routing['all']) ? array_merge($primary_args, $routing['all']) : $primary_args;

        // dispatch
        return $this->dispatch(
            $routing['controller'], $routing['action'], $args
        );
    }

    /**
     * Dispatch a controller's action, passing it arguments
     *
     * @param   string      $controller_classname
     * @param   string      $action
     * @param   array|null  $arguments
     * @return  void
     * @throws  \CarteBlanche\Exception\NotFoundException if the controller or the action is not found
     * @throws  \CarteBlanche\Exception\RuntimeException if the controller's action doesn't return required objects
     */
    public function dispatch($controller_classname, $action = 'index', array $arguments = null)
    {
        // load controller
        $_ctrl = CarteBlanche::getContainer()
            ->get('locator')->locateController($controller_classname);
        if (!empty($_ctrl) && class_exists($_ctrl)) {
            $this
                ->setControllerName($_ctrl)
                ->setController(new $_ctrl(CarteBlanche::getContainer()));
        } else {
            throw new \CarteBlanche\Exception\NotFoundException(
                sprintf("Controller '%s' can't be found!", $controller_classname)
            );
        }

        // load action
        if (false===strpos($action, 'Action')) {
            $action .= 'Action';
        }
        $this->setActionName($action);

        // dispatch
        if (method_exists($this->getController(), $this->getActionName())) {
            $result = CodeHelper::fetchArguments(
                $this->getActionName(), $arguments, $this->getController()
            );

            // controller method return treatment
                // string : content
            if (is_string($result)) {
                $this->render(array('output'=>$result));

                // array
            } elseif (is_array($result)) {
                // array : ( output , ... )
                if (isset($result['output'])) {
                    $this->render($result);

                // array : ( view , params )
                } else {
                    $template = $result[0];
                    $params = isset($result[1]) ? $result[1] : array();
                    $content = $this->view($template, $params);
                    if (!empty($content)) {
                        $tpl_params = array('output'=>$content);
                        if (isset($params['title'])) {
                            $tpl_params['title'] = $params['title'];
                        }
                        $this->render($tpl_params);
                    } else {
                        throw new \CarteBlanche\Exception\RuntimeException(
                            sprintf("A controller action must return an array like (view file, params) in [%s -> %s]!",
                                get_class($this->getController()), $this->getActionName())
                        );
                    }
                }

                // response object
            } elseif (is_object($result) && ($result instanceof \CarteBlanche\App\Response)) {
                CarteBlanche::getContainer()->set('response', $return, true);
                $this->render();

                // error
            } else {
                throw new \CarteBlanche\Exception\RuntimeException(
                    sprintf("A controller action must return a string, an array or a response object in [%s -> %s]!",
                        get_class($this->getController()), $this->getActionName())
                );
            }

        } else {
            throw new \CarteBlanche\Exception\NotFoundException(
                sprintf("Action '%s' can't be found in controller '%s'!", $this->getActionName(), get_class($this->getController()))
            );
        }
    }

    /**
     * Render the full application page view
     *
     * @param   array       $params     An array of the parameters passed for the view parsing
     * @param   bool/str    $debug      Object to debug if so
     * @param   null/\Exception  $exception
     * @return  string
     * @see     self::view()
     */
    public function render($params = null, $debug = null, $exception = null)
    {
        $_router 	= CarteBlanche::getContainer()->get('router');
        $_db 		= CarteBlanche::getContainer()->get('database');
        $request 	= CarteBlanche::getContainer()->get('request');
        $session 	= CarteBlanche::getContainer()->get('session');

        $_dbg = $request->getGet('debug');
        if (empty($debug) && isset($_GET['debug']))
            $debug = $_GET['debug'];

        if (empty($params['title'])) {
            if (!empty($_router->routes['action']))
                $params['title'] = $_router->routes['action'];
            else
                $params['title'] = 'Welcome';
        }
        $params['page_title'] = Helper::buildPageTitle($params['title']);
        if ($_db) {
            $params['db_queries'] = $_db->getCache();
        }
        $params['profiler'] = Helper::getProfiler();
        $params['altdb'] = $request->getUrlArg('altdb');
        $params['controller'] = get_class($this->getController());
        $params['action'] = $this->getActionName();

        $params['usersession'] = $session->getAttributes();
        $params['flashsession'] = $session->getBackup('flashes');
        if ($session->hasFlash())
            $params['flash_messages'] = $this->getFlashMessages();

        $params['charset'] = CarteBlanche::getConfig('html.charset', 'utf-8', true);

        $i18n_cfg = CarteBlanche::getConfig('i18n', array(), true);
        $languages_cfg = CarteBlanche::getConfig('languages', array(), true);
        $i18n = CarteBlanche::getContainer()->get('i18n');
        if ($i18n) {
            $default_lang = CarteBlanche::getContainer()->get('i18n')->getLocale();
            $lang_data = isset($languages_cfg[$default_lang]) ? $languages_cfg[$default_lang] : null;
            $params['lang'] = CarteBlanche::getContainer()->get('i18n')->getLanguageCode()
                .'-'.CarteBlanche::getContainer()->get('i18n')->getRegionCode();
            if (!empty($lang_data) && isset($lang_data['charset'])) {
                $params['charset'] = $lang_data['charset'];
            }
        }

        $params['request'] = \Library\Helper\Url::getRequestUrl();

        $_app_globals = CarteBlanche::getConfig('globals', null, true);
        $_cfg_globals = CarteBlanche::getConfig('globals');
        $_globals = array_merge($_app_globals, $_cfg_globals);
        if (!empty($_globals)) {
            $params['_globals'] = $_globals;
            if (!empty($_globals['meta_title']))
                Response::header('X-Website: '.$_globals['meta_title']);
        }

        $_app = CarteBlanche::getContainer()->get('config')
            ->getRegistry()->dumpStack('app');
        if (!empty($_app['app'])) {
            $params['_app'] = $_app['app'];
            if (!empty($_app['app']['name']))
                Response::header('Composed-by: '
                    .$_app['app']['name'].(!empty($_app['app']['version']) ? ' '.$_app['app']['version'] : '')
                );
        }

        $_manifest = CarteBlanche::getContainer()->get('config')
            ->getRegistry()->dumpStack('manifest');
        if (!empty($_manifest['manifest'])) {
            $params['manifest'] = $_app['manifest'];
        }

        $router_views = array();
        $_views = CarteBlanche::getContainer()->get('config')
            ->getRegistry()->dumpStack('views');
        if (!empty($_views)) {
            foreach ($_views as $_view) {
                $viewid = isset($_view['tpl']) ? $_view['tpl'] : uniqid();
                if (!isset($router_views[$viewid])) {
                    $router_views[$viewid] = $_view;
                    $router_views[$viewid]['iterations'] = 1;
                } else {
                    $router_views[$viewid]['iterations']++;
                }
            }
        }

//        \CarteBlanche\App\Debugger::shutdown(true, '\CarteBlanche\App\FrontController::renderExceptionStatic', $params);
        if (isset($exception)) $this->renderError($params, $exception);
        elseif (isset($debug)) $this->renderDebug($params, $debug);

        $ctrl_class = get_class($this->getController());
        if (property_exists($ctrl_class, 'template')) $tpl = $ctrl_class::$template;
        else trigger_error( "No template defined for controller '$ctrl_class'!", E_USER_ERROR );
        $router_views[] = array(
            'tpl'=>$tpl,
            'params'=>$params
        );
        $params['router_views'] = $router_views;
        return $this->view($tpl, $params, true, true);
    /*
        $router_views[] = array(
            'tpl'=>$tpl,
            'params'=>$params
        );
        $ctrl_class = get_class($this->controller);
        $params['router_views'] = $router_views;
        if (property_exists($ctrl_class, 'template')) return $this->view( $ctrl_class::$template, $params, true, true);
        else trigger_error( "No template defined for controller '$ctrl_class'!", E_USER_ERROR );
    */
    }

    /**
     * Render an application error page view
     *
     * @param   array       $params     An array of the parameters passed for the view parsing (passed by reference)
     * @param   int         $code       The error code
     * @param   null/string $exception    The exception info
     * @return  mixed
     * @see     self::render()
     * @see     self::view()
     */
    public function renderError($params = null, $code = 404, $exception = null)
    {
        $mode_data = CarteBlanche::getKernelMode(true);
        if (!is_array($mode_data)
            || !isset($mode_data['debug'])
            || false==$mode_data['debug']
        ) {
            $action = 'error'.$code.'Action';
            $_ctrl = CarteBlanche::getContainer()
                ->get('locator')->locateController('Error');
            if (!empty($_ctrl) && class_exists($_ctrl)) {
                $_routes['controller'] = $_ctrl;
                $this->setController(new $_ctrl(CarteBlanche::getContainer()));
            } else {
                trigger_error("Controller 'Error' can't be found!", E_USER_ERROR);
            }

            // don't execute `shutdown` kernel steps
            CarteBlanche::getContainer()->get('kernel')
                ->setShutdown(false);

            return CodeHelper::fetchArguments(
                $action, $params, $this->getController()
            );
        } else {
            if (!empty($exception)) {
                if (!isset($params['message'])) $params['message'] = '';
                $params['message'] .= method_exists($exception, 'getAppMessage') ?
                    $exception->getAppMessage() : $exception->getMessage();
            }
            $debug = \CarteBlanche\App\Debugger::getInstance();
            $this->renderDebug($params, null, false);
            $params['title'] = $debug->getDebuggerTitle();
            $tpl = 'profiler/template_profiler';

            // don't execute `shutdown` kernel steps
//            CarteBlanche::getContainer()->get('kernel')->setShutdown(false);

            return $this->view($tpl, $params, true, true);
        }
    }

    public function renderProductionError()
    {
        return call_user_func_array(array($this, 'renderError'), func_get_args());
    }

    /**
     * Render a debug application page view
     *
     * @param   array           $params         An array of the parameters passed for the view parsing (passed by reference)
     * @param   string/array    $dbg            Which object variable to debug (default is 'all')
     * @param   bool            $parse_template Parse the debug template or not (default is TRUE)
     * @return  string
     * @see     self::render()
     */
    public function renderDebug(&$params = null, $dbg = 'all', $parse_template = true)
    {
        $all_debug_infos = array( 'backtrace', 'php', 'server', 'session', 'constants', 'headers', 'system', 'router', 'registry', 'db' );
        if (!is_array($dbg)) {
            if ($dbg=='all' || $dbg==1) $dbg = $all_debug_infos;
            else $dbg = array( $dbg );
        }
        $debug = \CarteBlanche\App\Debugger::getInstance();
        foreach($dbg as $_dbg_info) {
            switch($_dbg_info) {
                case 'router':
                    $debug->addStack('object', CarteBlanche::getContainer()->get('router'), 'Router Object Dump');
                    break;
                case 'registry':
                    $debug->addStack('object', CarteBlanche::getContainer()->get('config')->getRegistry(), 'Registry Object Dump');
                    break;
                case 'db':
                    $debug->addStack('object', CarteBlanche::getContainer()->get('database'), 'Database Object Dump');
                    break;
                default:break;
            }
        }
        $params['debug'] = $debug;
        if ($parse_template) {
            $url_ref = str_replace('&', '&', $_SERVER['HTTP_REFERER']);
            $params['return_url'] = $url_ref;
            $debug->setDebuggerTitle( 'CarteBlanche - Debugging request '.str_replace(_ROOTHTTP, '/', $url_ref).'' );
            $params['title'] = $debug->getDebuggerTitle();
            $tpl = 'profiler/template_profiler';
            return $this->view( $tpl, $params, true, true);
        }
        return;
    }

    /**
     * Treat the session flash messages constructing for each one an array like:
     *     array(
     *         'class'=> 'class name',
     *         'content'=> 'content string'
     *     )
     *
     * @return array An array of all flash messages
     */
    public function getFlashMessages()
    {
        $session    = CarteBlanche::getContainer()->get('session');
        $flash_msgs = array();
        foreach($session->allFlashes() as $msg) {
            if (preg_match('/^([a-z]*):(.*)/i', $msg, $matches)) {
                if (count($matches==3)) {
                    $flash_msgs[] = array(
                        'class'=> $matches[1],
                        'content'=> $matches[2]
                    );
                }
            }
        }
        return $flash_msgs;
    }

    /**
     * Render a view
     *
     * @param   string      $view      The view filename in `$views_dir`
     * @param   array       $params    An array of the parameters passed for the view parsing
     * @param   bool        $display   Display the view or return it (default is FALSE)
     * @param   bool        $exit      Exit after rendering (default is FALSE)
     * @return  string|void
     * @see     \TemplateEngine\TemplateEngine::render()
     */
    public function view($view = null, array $params = array(), $display = false, $exit = false)
    {
        $view_file = CarteBlanche::getContainer()
            ->get('locator')->locateView( $view );
        CarteBlanche::getContainer()->get('config')
            ->getRegistry()->loadStack('views');
        CarteBlanche::getContainer()->get('config')
            ->getRegistry()->setEntry(uniqid(), array(
                'tpl'=>$view,
                'params'=>$params
            ));
        CarteBlanche::getContainer()->get('config')
            ->getRegistry()->saveStack('views', true);
        $output = CarteBlanche::getContainer()->get('template_engine')
            ->render($view_file, $params, $display, $exit);

        if ($display===true) {
            CarteBlanche::getContainer()->get('response')
                ->addContent(null, $output)
                ->send();
            return;
        } else {
            return $output;
        }
    }

// ---------------------
// User settings
// ---------------------

    /**
     * @return self
     */
    protected function _processQueryArguments()
    {
        $args = CarteBlanche::getContainer()->get('request')->getArguments();
        if (!empty($args)) {
            $this->_parseUserSettings($args);
        }
        return $this;
    }

    /**
     * @return self
     */
    protected function _processSessionValues()
    {
        $session = CarteBlanche::getContainer()->get('session');
        $session_data = $session->getSessionTable();
        if (!empty($session_data[$session::SESSION_ATTRIBUTESNAME])) {
            $this->_parseUserSettings($session_data[$session::SESSION_ATTRIBUTESNAME]);
        }
        return $this;
    }

    /**
     * @param  array   $args
     * @return  void
     */
    protected function _parseUserSettings(array $args)
    {
        if (!empty($args)) {
            foreach ($args as $param=>$value) {

                if ($param==='lang') {
                    $langs = CarteBlanche::getContainer()->get('i18n')->getAvailableLanguages();
                    if (array_key_exists($value, $langs)) {
                        CarteBlanche::getContainer()->get('i18n')->setLanguage($value);
                        $session = CarteBlanche::getContainer()->get('session');
                        $true_language = CarteBlanche::getContainer()->get('i18n')->getLanguage();
                        if (!$session->has('lang') || $session->get('lang')!==$true_language) {
                            $session->set('lang', $true_language);
                        }
                    }
                }

            }
        }
    }

}

// Endfile


<?php

namespace Scipper\Classloader;

/**
 * Class Classloader
 *
 * @author Steffen Kowalski <scipper@myscipper.de>
 *
 * @namespace Scipper\Classloader
 * @package Scipper\Classloader
 */
class Classloader {

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

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

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

	/**
	 * Classloader constructor.
	 *
	 * @param null $includePath
	 * @param string $directorySeparator
	 * @param string $fileExtension
	 */
	public function __construct($includePath = null, $directorySeparator = DIRECTORY_SEPARATOR, $fileExtension = ".php") {
		$this->includePath = $includePath;
		$this->directorySeparator = $directorySeparator;
		$this->fileExtension = $fileExtension;
	}

	/**
	 *
	 */
	public function register() {
		spl_autoload_register(array($this, 'autoload'));
	}

	/**
	 *
	 */
	public function unregister() {
		spl_autoload_unregister(array($this, 'autoload'));
	}

	/**
	 * @param string $class
	 *
	 * @return bool
	 */
	public function autoload($class) {
		$class = str_replace("\\", $this->directorySeparator, $class);
		$file = $this->includePath . $this->directorySeparator . $class . $this->fileExtension;
		if(is_readable($file)) {
			require_once $file;
			return true;
		}
		return false;
	}

}

?>


<?php namespace Way\Generators\Commands;

use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Way\Generators\Parsers\MigrationNameParser;
use Way\Generators\Parsers\MigrationFieldsParser;
use Way\Generators\Generator;
use Way\Generators\SchemaCreator;
use Config;

class MigrationGeneratorCommand extends GeneratorCommand {

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

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

    /**
     * @var \Way\Generators\ModelGenerator
     */
    protected $generator;

    /**
     * @var MigrationNameParser
     */
    private $migrationNameParser;

    /**
     * @var SchemaWriter
     */
    private $schemaCreator;

    /**
     * @param Generator $generator
     * @param MigrationNameParser $migrationNameParser
     * @param MigrationFieldsParser $migrationFieldsParser
     * @param SchemaCreator $schemaCreator
     */
    public function __construct(
        Generator $generator,
        MigrationNameParser $migrationNameParser,
        MigrationFieldsParser $migrationFieldsParser,
        SchemaCreator $schemaCreator
    )
    {
        $this->generator = $generator;
        $this->migrationNameParser = $migrationNameParser;
        $this->migrationFieldsParser = $migrationFieldsParser;
        $this->schemaCreator = $schemaCreator;

        parent::__construct($generator);
    }

    /**
     * Execute the console command
     */
    public function fire()
    {
        parent::fire();

        // Now that the file has been generated,
        // let's run dump-autoload to refresh everything
        if ( ! $this->option('testing'))
        {
            $this->call('dump-autoload');
        }
    }

    /**
     * The path where the file will be created
     *
     * @return mixed
     */
    protected function getFileGenerationPath()
    {
        $path = $this->getPathByOptionOrConfig('path', 'migration_target_path');
        $fileName = $this->getDatePrefix() . '_' . $this->argument('migrationName') . '.php';

        return "{$path}/{$fileName}";
    }

    /**
     * Get the date prefix for the migration.
     *
     * @return string
     */
    protected function getDatePrefix()
    {
        return date('Y_m_d_His');
    }

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

        // This will tell us the table name and action that we'll be performing
        $migrationData = $this->migrationNameParser->parse($migrationName);

        // We also need to parse the migration fields, if provided
        $fields = $this->migrationFieldsParser->parse($this->option('fields'));

        return [
            'CLASS' => ucwords(camel_case($migrationName)),
            'UP'    => $this->schemaCreator->up($migrationData, $fields),
            'DOWN'  => $this->schemaCreator->down($migrationData, $fields)
        ];
    }

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

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return array(
            array('migrationName', InputArgument::REQUIRED, 'The migration name')
        );
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return array(
            ['fields', null, InputOption::VALUE_OPTIONAL, 'Fields for the migration'],
            ['path', null, InputOption::VALUE_OPTIONAL, 'Where should the file be created?'],
            ['templatePath', null, InputOption::VALUE_OPTIONAL, 'The location of the template for this generator'],
            ['testing', null, InputOption::VALUE_OPTIONAL, 'For internal use only.']
        );
    }

}


<?php namespace Ordercloud\Entities\Users;

use JsonSerializable;
use Ordercloud\Entities\Addresses\NamedCoordinatedAddress;

class NewUserAddress extends NamedCoordinatedAddress implements JsonSerializable
{
    /**
     * @var string
     */
    private $note;
    /**
     * @var bool
     */
    private $favourite;

    /**
     * @param string $name
     * @param string $streetNumber
     * @param string $streetName
     * @param string $complex
     * @param string $suburb
     * @param string $city
     * @param string $postalCode
     * @param string $note
     * @param string $latitude
     * @param string $longitude
     * @param bool   $favourite
     */
    public function __construct($name, $streetNumber, $streetName, $complex, $suburb, $city, $postalCode, $note, $latitude, $longitude, $favourite = false)
    {
        parent::__construct(
            $streetNumber,
            $streetName,
            $complex,
            $suburb,
            $city,
            $postalCode,
            $name,
            $latitude,
            $longitude
        );
        $this->note = $note;
        $this->favourite = $favourite;
    }

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

    /**
     * @return boolean
     */
    public function isFavourite()
    {
        return $this->favourite;
    }

    /**
     * Specify data which should be serialized to JSON
     */
    public function jsonSerialize()
    {
        $json = parent::jsonSerialize();

        $json['note'] = $this->getNote();
        $json['favourite'] = $this->isFavourite();

        return $json;
    }
}


<?php

namespace Philip\Plugin;

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

/**
 * Adds some canned responses to the Philip IRC bot.
 *
 * @author Sean Crystal <seancrystal@gmail.com>
 */
class CannedResponsePlugin extends BasePlugin
{
    /** @var array */
    private $responses = array();

    /** @var array */
    private $attributes = array('an IRC bot');

    /**
     * Init the plugin and start listening to messages
     */
    public function init()
    {
        $this->addResponses();

        $config = $this->bot->getConfig();

        // detects someone speaking to the bot
        $responses = $this->responses;
        $address_re = "/(^{$config['nick']}(.+)|(.+){$config['nick']}[!.?]*)$/i";
        $this->bot->onChannel($address_re, function(Event $event) use ($responses) {
            $matches = $event->getMatches();
            $message = $matches[1] ? $matches[1] : $matches[2];

            foreach ($responses as $regex => $function) {
                if (preg_match($regex, $message, $matches)) {
                    $event->addResponse(
                        Response::msg($event->getRequest()->getSource(), $function($matches))
                    );
                }
            }
        });
    }

    /**
     * Set up all the canned responses
     */
    private function addResponses()
    {
        // workaround for $this being unavailable in closures
        $plugin = $this;

        // basic response
        $this->addResponse('/i (love|<3) (you|u)/i', function($matches) {
            return 'Shutup baby, I know it!';
        });

        // matches things like "bot: you're the greatest thing ever!" and saves the attribute for later
        $this->addResponse('/(you are|you\'re) (the |a |an )*([\w ]+)/i', function($matches) use ($plugin) {
            $plugin->addAttribute(trim($matches[2] .' '. trim($matches[3])));
            return "No, *you're* {$matches[2]} ". trim($matches[3]) .'!';
        });

        // matches things like "bot is amazing!" and saves the attribute for later
        $this->addResponse('/is (the |a |an )*([\w ]+)/i', function($matches) use ($plugin) {
            $plugin->addAttribute(trim($matches[1] .' '. trim($matches[2])));
            return "No, *you're* {$matches[1]} ". trim($matches[2]) .'!';
        });

        // responds to things like "who is bot?" with a remembered attribute
        $this->addResponse('/(what|who) (are you|is)/i', function($matches) use ($plugin) {
            return "I'm ". $plugin->getRandomAttribute();
        });
    }

    /**
     * Get a random attribute
     */
    public function getRandomAttribute()
    {
        $index = rand(0, count($this->attributes) - 1);
        return $this->attributes[$index];
    }

    /**
     * Add an attribute to the bot
     *
     * @param string $attribute
     */
    public function addAttribute($attribute)
    {
        $this->attributes[] = $attribute;
    }

    /**
     * Helper method to add responses to the plugin
     *
     * @param string $regex
     * @param \Closure $function
     */
    private function addResponse($regex, $function)
    {
        $this->responses[$regex] = $function;
    }

    /**
     * @see \Philip\AbstractPlugin#getName()
     */
    public function getName()
    {
        return 'CannedResponsePlugin';
    }
}