<?php

namespace Soluble\Normalist\Synthetic;

use Soluble\Normalist\Synthetic\ResultSet\ResultSet;
use Soluble\Db\Sql\Select;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Expression;
use Zend\Db\Sql\Where;
use Zend\Db\Sql\Predicate;
use Zend\Db\Sql\PreparableSqlInterface;
use Zend\Db\Sql\SqlInterface;
use ArrayObject;

class Table
{
    /**
     * Table name
     * @var string
     */
    protected $table;

    /**
     * Prefixed table name or table name if no prefix
     * @var string
     */
    protected $prefixed_table;

    /**
     *
     * @var Table\Relation
     */
    protected $relation;

    /**
     * Primary key of the table
     * @var string|null
     */
    protected $primary_key;

    /**
     * Primary keys of the table in case there's a multiple column pk
     * @var array|null
     */
    protected $primary_keys;

    /**
     * Table alias useful when using join
     * @var string
     */
    protected $table_alias;

    /**
     * @param TableManager
     */
    protected $tableManager;

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

    /**
     *
     * @var Sql
     */
    protected $sql;

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

    /**
     *
     * @param string $table table name
     * @param TableManager $tableManager
     *
     * @throws Exception\InvalidArgumentException
     */
    public function __construct($table, TableManager $tableManager)
    {
        $this->tableManager = $tableManager;
        $this->sql = new Sql($tableManager->getDbAdapter());
        if (!is_string($table) || trim($table) == '') {
            throw new Exception\InvalidArgumentException(__METHOD__ . ": Table name must be a non-empty string");
        }

        $this->table = $table;
        $this->prefixed_table = $this->tableManager->getTablePrefix() . $table;
    }

    /**
     * Get a TableSearch object
     *
     * @param string $table_alias whenever you want to alias the table (useful in joins)
     * @return TableSearch
     */
    public function search($table_alias = null)
    {
        return new TableSearch($this->select($table_alias), $this);
    }

    /**
     * Return all records in the table
     *
     * @return ResultSet
     */
    public function all()
    {
        return $this->search()->execute();
    }

    /**
     * Find a record
     *
     * @param integer|string|array $id
     *
     * @throws Exception\InvalidArgumentException when id is invalid
     * @throws Exception\PrimaryKeyNotFoundException
     *
     * @return Record|false
     */
    public function find($id)
    {
        $record = $this->findOneBy($this->getPrimaryKeyPredicate($id));
        return $record;
    }

    /**
     * Find a record by primary key, throw a NotFoundException if record does not exists
     *
     * @param integer|string|array $id
     *
     * @throws Exception\NotFoundException
     * @throws Exception\InvalidArgumentException when the id is not valid
     * @throws Exception\PrimaryKeyNotFoundException
     *
     * @return Record
     */
    public function findOrFail($id)
    {
        $record = $this->find($id);
        if ($record === false) {
            throw new Exception\NotFoundException(__METHOD__ . ": cannot find record '$id' in table '$this->table'");
        }
        return $record;
    }

    /**
     * Find a record by unique key
     *
     * @param  Where|\Closure|string|array|Predicate\PredicateInterface $predicate
     * @param  string $combination One of the OP_* constants from Predicate\PredicateSet
     *
     * @throws Exception\ColumnNotFoundException when a column in the predicate does not exists
     * @throws Exception\MultipleMatchesException when more than one record match the predicate
     * @throws Exception\InvalidArgumentException when the predicate is not correct / invalid column
     * @throws \Zend\Db\Sql\Exception\InvalidArgumentException
     *
     * @return Record|false
     */
    public function findOneBy($predicate, $combination = Predicate\PredicateSet::OP_AND)
    {
        $select = $this->select($this->table);

        try {
            $select->where($predicate, $combination);
        } catch (\Zend\Db\Sql\Exception\InvalidArgumentException $e) {
            $message = "Invalid predicates or combination detected (" . $e->getMessage() . ")";
            throw new Exception\InvalidArgumentException(__METHOD__ . ": $message");
        }

        try {
            $results = $select->execute()
                              ->toArray();
        } catch (\Exception $e) {
            $messages = [];
            $ex = $e;
            do {
                $messages[] = $ex->getMessage();
            } while ($ex = $ex->getPrevious());
            $message = implode(', ', array_unique($messages));

            $lmsg = '[' . get_class($e) . '] ' . strtolower($message) . '(code:' . $e->getCode() . ')';

            if (strpos($lmsg, 'column not found') !== false ||
                    strpos($lmsg, 'unknown column') !== false) {
                //"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'media_id' in 'where clause
                $rex = new Exception\ColumnNotFoundException(__METHOD__ . ": $message");
                throw $rex;
            } else {
                $sql_string = $select->getSqlString($this->sql->getAdapter()->getPlatform());
                $iqex = new Exception\InvalidArgumentException(__METHOD__ . ": $message - $sql_string");
                throw $iqex;
            }
        }

        if (count($results) == 0) {
            return false;
        }
        if (count($results) > 1) {
            throw new Exception\MultipleMatchesException(__METHOD__ . ": return more than one record");
        }

        $record = $this->record($results[0]);
        $record->setState(Record::STATE_CLEAN);
        return $record;
    }

    /**
     * Find a record by unique key and trhow an exception id record cannot be found
     *
     * @param  Where|\Closure|string|array|Predicate\PredicateInterface $predicate
     * @param  string $combination One of the OP_* constants from Predicate\PredicateSet
     *
     * @throws Exception\NotFoundException when the record is not found
     * @throws Exception\ColumnNotFoundException when a column in the predicate does not exists
     * @throws Exception\MultipleMatchesException when more than one record match the predicate
     * @throws Exception\InvalidArgumentException when the predicate is not correct / invalid column
     *
     * @return Record
     */
    public function findOneByOrFail($predicate, $combination = Predicate\PredicateSet::OP_AND)
    {
        $record = $this->findOneBy($predicate, $combination);
        if ($record === false) {
            throw new Exception\NotFoundException(__METHOD__ . ": cannot findOneBy record in table '$this->table'");
        }
        return $record;
    }

    /**
     * Count the number of record in table
     *
     * @return int number of record in table
     */
    public function count()
    {
        return $this->countBy("1=1");
    }

    /**
     * Find a record by unique key
     *
     * @param  Where|\Closure|string|array|Predicate\PredicateInterface $predicate
     * @param  string $combination One of the OP_* constants from Predicate\PredicateSet
     * @throws Exception\InvalidArgumentException
     *
     * @return int number of record matching predicates
     */
    public function countBy($predicate, $combination = Predicate\PredicateSet::OP_AND)
    {
        $select = $this->select()
                        ->columns(['count' => new Expression('count(*)')]);

        try {
            $select->where($predicate, $combination);
        } catch (\Zend\Db\Sql\Exception\InvalidArgumentException $e) {
            $message = "Invalid predicates or combination detected (" . $e->getMessage() . ")";
            throw new Exception\InvalidArgumentException(__METHOD__ . ": $message");
        }

        $result = $select->execute()->toArray();

        return (int) $result[0]['count'];
    }

    /**
     * Test if a record exists
     *
     * @param integer|string|array $id
     *
     * @throws Exception\InvalidArgumentException when the id is invalid
     * @throws Exception\PrimaryKeyNotFoundException
     *
     * @return boolean
     */
    public function exists($id)
    {
        $result = $this->select()->where($this->getPrimaryKeyPredicate($id))
                        ->columns(['count' => new Expression('count(*)')])
                        ->execute()
                        ->toArray();

        return ($result[0]['count'] > 0);
    }

    /**
     * Test if a record exists by a predicate
     *
     * @param  Where|\Closure|string|array|Predicate\PredicateInterface $predicate
     * @param  string $combination One of the OP_* constants from Predicate\PredicateSet
     *
     * @throws Exception\InvalidArgumentException when the predicate is not correct
     *
     * @return boolean
     */
    public function existsBy($predicate, $combination = Predicate\PredicateSet::OP_AND)
    {
        try {
            $select = $this->select()->where($predicate, $combination)
                    ->columns(['count' => new Expression('count(*)')]);
            $result = $select->execute()
                             ->toArray();
        } catch (\Exception $e) {
            throw new Exception\InvalidArgumentException(__METHOD__ . ": invaid usage ({$e->getMessage()})");
        }

        return ($result[0]['count'] > 0);
    }

    /**
     * Get a select object (Soluble\Db\Select)
     *
     * @param string $table_alias useful when you want to join columns
     * @return Select
     */
    public function select($table_alias = null)
    {
        $prefixed_table = $this->prefixed_table;
        $select = new \Soluble\Db\Sql\Select();
        $select->setDbAdapter($this->tableManager->getDbAdapter());
        if ($table_alias === null) {
            $table_spec = $prefixed_table;
        } else {
            $table_spec = [$table_alias => $prefixed_table];
        }
        $select->from($table_spec);
        return $select;
    }

    /**
     * Delete by primary/unique key value
     *
     * @param integer|string|array $id primary key(s) or a Record object
     *
     * @throws Exception\InvalidArgumentException if $id is not valid
     * @return integer the number of affected rows (maybe be greater than 1 with triggers or cascade)
     */
    public function delete($id)
    {
        return $this->deleteBy($this->getPrimaryKeyPredicate($id));
    }

    /**
     * Delete a record by predicate
     *
     * @param  Where|\Closure|string|array|Predicate\PredicateInterface $predicate
     * @param  string $combination One of the OP_* constants from Predicate\PredicateSet
     *
     * @return integer the number of affected rows (can be be influenced by triggers or cascade)
     */
    public function deleteBy($predicate, $combination = Predicate\PredicateSet::OP_AND)
    {
        $delete = $this->sql->delete($this->prefixed_table)
                ->where($predicate, $combination);

        $statement = $this->sql->prepareStatementForSqlObject($delete);
        $result = $statement->execute();
        return $result->getAffectedRows();
    }

    /**
     * Delete a record or throw an Exception
     *
     * @param integer|string|array $id primary key value
     *
     * @throws Exception\InvalidArgumentException if $id is not valid
     * @throws Exception\NotFoundException if record does not exists
     *
     * @return Table
     */
    public function deleteOrFail($id)
    {
        $deleted = $this->delete($id);
        if ($deleted == 0) {
            throw new Exception\NotFoundException(__METHOD__ . ": cannot delete record '$id' in table '$this->table'");
        }
        return $this;
    }

    /**
     * Update data into table
     *
     * @param array|ArrayObject $data
     * @param  Where|\Closure|string|array|Predicate\PredicateInterface $predicate
     * @param  string $combination One of the OP_* constants from Predicate\PredicateSet
     * @param  boolean $validate_datatypes ensure all datatype are compatible with column definition
     *
     * @throws Exception\InvalidArgumentException
     * @throws Exception\ColumnNotFoundException when $data contains columns that does not exists in table
     * @throws Exception\ForeignKeyException when insertion failed because of an invalid foreign key
     * @throws Exception\DuplicateEntryException when insertion failed because of an invalid foreign key
     * @throws Exception\NotNullException when insertion failed because a column cannot be null
     * @throws Exception\RuntimeException when insertion failed for another reason
     *
     * @return int number of affected rows
     */

    public function update($data, $predicate, $combination = Predicate\PredicateSet::OP_AND, $validate_datatypes = false)
    {
        $prefixed_table = $this->prefixed_table;

        if ($data instanceof ArrayObject) {
            $d = (array) $data;
        } elseif (is_array($data)) {
            $d = $data;
        } else {
            throw new Exception\InvalidArgumentException(__METHOD__ . ": requires data to be array or an ArrayObject");
        }

        $this->checkDataColumns($d);

        if ($validate_datatypes) {
            $this->validateDatatypes($d);
        }

        $update = $this->sql->update($prefixed_table);
        $update->set($d);
        $update->where($predicate, $combination);

        $result = $this->executeStatement($update);

        return  $result->getAffectedRows();
    }

    /**
     * Insert data into table
     *
     * @param array|ArrayObject $data
     * @param boolean $validate_datatypes ensure data are compatible with database columns datatypes
     *
     * @throws Exception\InvalidArgumentException when data is not an array or an ArrayObject
     * @throws Exception\ColumnNotFoundException when $data contains columns that does not exists in table
     * @throws Exception\ForeignKeyException when insertion failed because of an invalid foreign key
     * @throws Exception\DuplicateEntryException when insertion failed because of an invalid foreign key
     * @throws Exception\NotNullException when insertion failed because a column cannot be null
     * @throws Exception\RuntimeException when insertion failed for another reason
     *
     * @return Record
     */
    public function insert($data, $validate_datatypes = false)
    {
        $prefixed_table = $this->prefixed_table;

        if ($data instanceof \ArrayObject) {
            $d = (array) $data;
        } elseif (is_array($data)) {
            $d = $data;
        } else {
            $type = gettype($data);
            throw new Exception\InvalidArgumentException(__METHOD__ . ": expects data to be array or ArrayObject. Type receive '$type'");
        }

        $this->checkDataColumns($d);

        if ($validate_datatypes) {
            $this->validateDatatypes($d);
        }

        $insert = $this->sql->insert($prefixed_table);
        $insert->values($d);

        $this->executeStatement($insert);

        $pks = $this->getPrimaryKeys();

        // Should never happen, as getPrimaryKeys throws Exception when no pk exists
        //@codeCoverageIgnoreStart
        if (!is_array($pks)) {
            $msg = __METHOD__ . " Error getting primary keys of table " . $this->table . ", require array, returned type is: " . gettype($pks) ;
            throw new Exception\UnexpectedValueException($msg);
        }
        //@codeCoverageIgnoreEnd

        $nb_pks = count($pks);
        if ($nb_pks > 1) {
            // In multiple keys there should not be autoincrement value
            $id = [];
            foreach ($pks as $pk) {
                $id[$pk] = $d[$pk];
            }
        } elseif (array_key_exists($pks[0], $d) && $d[$pks[0]] !== null) {
            // not using autogenerated value
            //$id = $d[$this->getPrimaryKey()];
            $id = $d[$pks[0]];
        } else {
            $id = $this->tableManager->getDbAdapter()->getDriver()->getLastGeneratedValue();
        }

        return $this->findOrFail($id);
    }

    /**
     * Insert on duplicate key
     *
     * @param array|ArrayObject $data
     * @param array|null $duplicate_exclude
     * @param boolean $validate_datatypes ensure data are compatible with database columns datatypes
     *
     * @throws Exception\ColumnNotFoundException
     * @throws Exception\RecordNotFoundException
     * @throws Exception\ForeignKeyException when insertion failed because of an invalid foreign key
     * @throws Exception\DuplicateEntryException when insertion failed because of an invalid foreign key
     * @throws Exception\NotNullException when insertion failed because a column cannot be null
     *
     * @return Record|false
     */
    public function insertOnDuplicateKey($data, array $duplicate_exclude = [], $validate_datatypes = false)
    {
        $platform = $this->tableManager->getDbAdapter()->platform;
/*
        $unique_keys   = $this->tableManager->metadata()->getUniqueKeys($this->prefixed_table);
        $unique_keys[]
        $primary_keys = $this->getPrimaryKeys();

        $uniques = array_merge(array($unique_keys, 'primary_keys' => $primary_keys));
        $cols = ['index2', 'index1'];
        $data = ['index1' => 'coool', 'index2' => 'hello'];

        $unique_found = false;
        $data_columns = array_keys($data);
        var_dump($uniques);
        while ($cols = array_pop($uniques) && !$unique_found) {

            var_dump($cols);
            die();
            $intersect = array_intersect($cols, $data_columns);
            $unique_found = ($intersect == $data_columns);
        }

        dump($intersect);

        die();
        $intersect = array_intersect($cols, array_keys($data));
        dump($intersect);
        dump($intersect == $cols);
        die();

        foreach($unique_keys as $index => $columns) {
        //    if (array_diff_key($duplicate_exclude, $d))

        }
dump($unique_keys);
dump($primary_keys);
die();
 *
 */
        $primary = $this->getPrimaryKey();

        if ($data instanceof ArrayObject) {
            $d = (array) $data;
        } else {
            $d = $data;
        }

        $this->checkDataColumns($d);
        $this->checkDataColumns(array_fill_keys($duplicate_exclude, null));

        if ($validate_datatypes) {
            $this->validateDatatypes($d);
        }

        $insert = $this->sql->insert($this->prefixed_table);
        $insert->values($d);
        $sql_string = $this->sql->getSqlStringForSqlObject($insert);

        $extras = [];

        /**
         * No reason to exclude primary key from
         */
        //$excluded_columns = array_merge($duplicate_exclude, array($primary));
        $excluded_columns = $duplicate_exclude;

        foreach ($d as $column => $value) {
            if (!in_array($column, $excluded_columns)) {
                $v = ($value === null) ? 'NULL' : $v = $platform->quoteValue($value);
                $extras[] = $platform->quoteIdentifier($column) . ' = ' . $v;
            }
        }
        $sql_string .= ' on duplicate key update ' . implode(',', $extras);

        try {
            $this->executeStatement($sql_string);
        } catch (\Exception $e) {
            $messages = [];
            $ex = $e;
            do {
                $messages[] = $ex->getMessage();
            } while ($ex = $ex->getPrevious());
            $msg = implode(', ', array_unique($messages));
            $message = __METHOD__ . ": failed, $msg [ $sql_string ]";
            throw new Exception\RuntimeException($message);
        }

        if (array_key_exists($primary, $d)) {
            // not using autogenerated value
            $pk_value = $d[$primary];
            $record = $this->findOrFail($pk_value);
        } else {
            $id = $this->tableManager->getDbAdapter()->getDriver()->getLastGeneratedValue();

            // This test is not made with id !== null, understand why before changing
            if ($id > 0) {
                $pk_value = $id;
                $record = $this->find($pk_value);
            } else {
                // if the id was not generated, we have to guess on which key
                // the duplicate has been fired
                $unique_keys = $this->tableManager->metadata()->getUniqueKeys($this->prefixed_table);
                $data_columns = array_keys($d);

                // Uniques keys could be
                // array(
                //      'unique_idx' => array('categ', 'legacy_mapping'),
                //      'unique_idx_2' => array('test', 'test2')
                //      )
                //

                $record = false;

                foreach ($unique_keys as $index_name => $unique_columns) {
                    //echo "On duplicate key\n\n $index_name \n";
                    $intersect = array_intersect($data_columns, $unique_columns);
                    if (count($intersect) == count($unique_columns)) {
                        // Try to see if we can find a record with the key
                        $conditions = [];
                        foreach ($intersect as $key) {
                            $conditions[$key] = $d[$key];
                        }
                        $record = $this->findOneBy($conditions);
                        if ($record) {
                            //$found = true;
                            //$pk_value = $record[$primary];
                            break;
                        }
                    }
                }

                // I cannot write a test case for that
                // It should never happen but in case :
                //@codeCoverageIgnoreStart
                if (!$record) {
                    throw new \Exception(__METHOD__ . ": after probing all unique keys in table '{$this->table}', cannot dertermine which one was fired when using on duplicate key.");
                }
                //@codeCoverageIgnoreEnd
            }
        }
        return $record;
    }

    /**
     * Return table relations (foreign keys infos)
     *
     * @return array
     */
    public function getRelations()
    {
        return $this->tableManager->metadata()->getForeignKeys($this->prefixed_table);
    }

    /**
     * Return a record object for this table
     * If $data is specified, the record will be filled with the
     * data present in the associative array
     *
     *
     * If $throwException is true, if any non existing column is found
     * an error will be thrown
     *
     * @throws Exception\ColumnNotFoundException if $ignore_invalid_columns is false and some columns does not exists in table
     *
     * @param array|ArrayObject $data associative array containing initial data
     * @param boolean $ignore_invalid_columns if true will throw an exception if a column does not exists
     * @return Record
     */
    public function record($data = [], $ignore_invalid_columns = true)
    {
        if (!$ignore_invalid_columns) {
            $this->checkDataColumns((array) $data);
        }
        $record = new Record((array) $data, $this);

        $record->setState(Record::STATE_NEW);
        return $record;
    }

    /**
     * Return table relation reader
     *
     * @return Table\Relation
     */
    public function relation()
    {
        if ($this->relation === null) {
            $this->relation = new Table\Relation($this);
        }
        return $this->relation;
    }

    /**
     * Return table primary keys
     *
     * @throws Exception\PrimaryKeyNotFoundException when no pk
     * @throws Exception\RuntimeException when it cannot determine primary key on table
     *
     *
     * @return array
     */
    public function getPrimaryKeys()
    {
        if ($this->primary_keys === null) {
            try {
                $this->primary_keys = $this->tableManager->metadata()->getPrimaryKeys($this->prefixed_table);
            } catch (\Soluble\Schema\Exception\NoPrimaryKeyException $e) {
                throw new Exception\PrimaryKeyNotFoundException(__METHOD__ . ': ' . $e->getMessage());
            //@codeCoverageIgnoreStart
            } catch (\Soluble\Schema\Exception\ExceptionInterface $e) {
                throw new Exception\RuntimeException(__METHOD__ . ": Cannot determine primary key on table " . $this->prefixed_table);
            }
            //@codeCoverageIgnoreEnd
        }
        return $this->primary_keys;
    }

    /**
     * Return primary key, if multiple primary keys found will
     * throw an exception
     *
     * @throws Exception\PrimaryKeyNotFoundException when no pk found
     * @throws Exception\MultiplePrimaryKeysFoundException when multiple primary keys found
     * @throws Exception\RuntimeException when it cannot determine primary key on table
     *
     * @return int|string
     */
    public function getPrimaryKey()
    {
        if ($this->primary_key === null) {
            $pks = $this->getPrimaryKeys();
            if (count($pks) > 1) {
                throw new Exception\MultiplePrimaryKeysFoundException(__METHOD__ . ": Error getting unique primary key on table, multiple found on table " . $this->prefixed_table);
            }
            $this->primary_key = $pks[0];
        }
        return $this->primary_key;
    }

    /**
     * Return list of table columns
     *
     * @throws Soluble\Db\Metadata\Exception\InvalidArgumentException
     * @throws Soluble\Db\Metadata\Exception\ErrorException
     * @throws Soluble\Db\Metadata\Exception\ExceptionInterface
     * @throws Soluble\Db\Metadata\Exception\TableNotFoundException
     *
     * @return array
     */
    public function getColumnsInformation()
    {
        if ($this->column_information === null) {
            $this->column_information = $this->tableManager
                    ->metadata()
                    ->getColumnsInformation($this->prefixed_table);
        }
        return $this->column_information;
    }

    /**
     * Return the original table name
     *
     * @return string
     */
    public function getTableName()
    {
        return $this->table;
    }

    /**
     * Return the prefixed table
     *
     * @return string
     */
    public function getPrefixedTableName()
    {
        return $this->prefixed_table;
    }

    /**
     * Return underlying table manager
     *
     * @return TableManager
     */
    public function getTableManager()
    {
        return $this->tableManager;
    }

    /**
     * Execute a statement
     *
     * @todo move to driver if not will only support MySQL
     *
     * @throws Exception\ForeignKeyException when insertion failed because of an invalid foreign key
     * @throws Exception\DuplicateEntryException when insertion failed because of an invalid foreign key
     * @throws Exception\NotNullException when insertion failed because a column cannot be null
     * @throws Exception\RuntimeException when insertion failed for another reason
     *
     * @param string|PreparableSqlInterface $sqlObject
     * @return \Zend\Db\Adapter\Driver\ResultInterface
     */
    protected function executeStatement($sqlObject)
    {
        if ($sqlObject instanceof PreparableSqlInterface) {
            $statement = $this->sql->prepareStatementForSqlObject($sqlObject);
        } elseif (is_string($sqlObject)) {
            $statement = $this->tableManager->getDbAdapter()->createStatement($sqlObject);
        } else {
            //@codeCoverageIgnoreStart
            throw new Exception\InvalidArgumentException(__METHOD__ . ': expects sqlObject to be string or PreparableInterface');
             //@codeCoverageIgnoreEnd
        }
        try {
            $result = $statement->execute();
        } catch (\Exception $e) {
            // In ZF2, PDO_Mysql and MySQLi return different exception,
            // attempt to normalize by catching one exception instead
            // of RuntimeException and InvalidQueryException

            $messages = [];
            $ex = $e;
            do {
                $messages[] = $ex->getMessage();
            } while ($ex = $ex->getPrevious());
            $message = implode(', ', array_unique($messages));

            $lmsg = __METHOD__ . ':' . strtolower($message) . '(code:' . $e->getCode() . ')';

            if (strpos($lmsg, 'cannot be null') !== false) {
                // Integrity constraint violation: 1048 Column 'non_null_column' cannot be null
                $rex = new Exception\NotNullException(__METHOD__ . ': ' . $message, $e->getCode(), $e);
                throw $rex;
            } elseif (strpos($lmsg, 'duplicate entry') !== false) {
                $rex = new Exception\DuplicateEntryException(__METHOD__ . ': ' . $message, $e->getCode(), $e);
                throw $rex;
            } elseif (strpos($lmsg, 'constraint violation') !== false ||
                    strpos($lmsg, 'foreign key') !== false) {
                $rex = new Exception\ForeignKeyException(__METHOD__ . ': ' . $message, $e->getCode(), $e);
                throw $rex;
            } else {
                if ($sqlObject instanceof SqlInterface) {
                    $sql_string = $sqlObject->getSqlString($this->sql->getAdapter()->getPlatform());
                } else {
                    $sql_string = $sqlObject;
                }
                $iqex = new Exception\RuntimeException(__METHOD__ . ': ' . $message . "[$sql_string]", $e->getCode(), $e);
                throw $iqex;
            }
        }
        return $result;
    }

    /**
     * Return primary key predicate
     *
     * @param integer|string|array $id
     *
     * @throws Exception\InvalidArgumentException
     * @throws Exception\PrimaryKeyNotFoundException
     * @return array predicate
     */
    protected function getPrimaryKeyPredicate($id)
    {
        if (!is_scalar($id) && !is_array($id)) {
            throw new Exception\InvalidArgumentException(__METHOD__ . ": Id must be scalar or array, type " . gettype($id) . " received");
        }
        $keys = $this->getPrimaryKeys();
        if (count($keys) == 1) {
            $pk = $keys[0];
            if (!is_scalar($id)) {
                $type = gettype($id);
                throw new Exception\InvalidArgumentException(__METHOD__ . ": invalid primary key value. Table '{$this->table}' has a single primary key '$pk'. Argument must be scalar, '$type' given");
            }
            $predicate = [$pk => $id];
        } else {
            if (!is_array($id)) {
                $pks = implode(',', $keys);
                $type = gettype($id);
                throw new Exception\InvalidArgumentException(__METHOD__ . ": invalid primary key value. Table '{$this->table}' has multiple primary keys '$pks'. Argument must be an array, '$type' given");
            }

            $matched_keys = array_diff($id, $keys);
            if (count($matched_keys) == count($keys)) {
                $predicate = $matched_keys;
            } else {
                $pks = implode(',', $keys);
                $vals = implode(',', $matched_keys);
                throw new Exception\InvalidArgumentException(__METHOD__ . ": incomplete primary key value. Table '{$this->table}' has multiple primary keys '$pks', values received '$vals'");
            }
        }
        return $predicate;
    }

    /**
     * Check if all columns exists in table
     *
     * @param array $data
     * @throws Exception\ColumnNotFoundException
     */
    protected function checkDataColumns(array $data)
    {
        $diff = array_diff_key($data, $this->getColumnsInformation());
        if (count($diff) > 0) {
            $msg = implode(',', array_keys($diff));
            throw new Exception\ColumnNotFoundException(__METHOD__ . ": some specified columns '$msg' does not exists in table {$this->table}.");
        }
    }

    /**
     * Validate data with database column datatype
     *
     * @param array $data
     * @return void
     */
    protected function validateDatatypes(array $data)
    {
        // @todo code for validating datatypes
        // integer -> numeric
        // etc, etc...
        $columnInfo = $this->getColumnsInformation();
        foreach ($data as $column => $value) {
            // checks on types
        }
    }
}


<?php

declare(strict_types=1);

namespace Ruwork\PaginatorBundle\DependencyInjection\Compiler;

use Ruwork\Paginator\Paginator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class AddTwigPathPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container): void
    {
        if (!$container->hasDefinition('twig.loader.native_filesystem')) {
            return;
        }

        $path = \dirname((new \ReflectionClass(Paginator::class))->getFileName()).'/Resources/templates';

        $container->getDefinition('twig.loader.native_filesystem')
            ->addMethodCall('addPath', [$path, 'RuworkPaginator']);
    }
}


<?php

/**
 * @author    Markus Tacker <m@coderbyheart.com>
 */

namespace Coderbyheart\MailChimpBundle\MailChimp;

use Buzz\Browser;
use Buzz\Client\ClientInterface;
use Buzz\Client\Curl;
use Coderbyheart\MailChimpBundle\Exception\BadMethodCallException;
use Coderbyheart\MailChimpBundle\Exception\InvalidArgumentException;

class Api
{
    /**
     * @constant string
     */
    const RETURN_OBJECT = 'object';

    /**
     * @constant string
     */
    const RETURN_ARRAY = 'array';

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

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

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

    /**
     * @var string
     */
    private $format = 'json';

    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * Constructor.
     *
     * @param string $apiKey MailChimp API key.
     *
     * @throws BadMethodCallException
     */
    public function __construct($apiKey)
    {
        if (!preg_match('/^[0-9a-f]+-[0-9a-z]+$/', $apiKey)) {
            throw new BadMethodCallException(sprintf('Api key "%s" has invalid format.', $apiKey));
        }

        list(, $this->dataCenter) = explode('-', $apiKey);

        $this->apiKey     = $apiKey;
        $this->returnType = static::RETURN_OBJECT;
    }

    protected function post($endpoint, $args)
    {
        $request     = array_merge($args, array(
            'apikey' => $this->apiKey,
        ));
        $requestData = json_encode($request);
        $client      = $this->getClient();
        $browser     = new Browser($client);
        $response    = $browser->post(
            sprintf('https://%s.api.mailchimp.com/2.0/%s.%s', $this->dataCenter, $endpoint, $this->format),
            array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($requestData),
                'User-Agent: CoderbyheartMailChimpBundle',
                'Accept: application/json'
            ),
            $requestData
        );

        switch ($this->returnType) {
            case static::RETURN_ARRAY:
                list($status, $errorMessage, $data) = $this->parseAsArray($response->getContent());
                break;
            default:
                list($status, $errorMessage, $data) = $this->parseAsObject($response->getContent());
        }

        if ('error' === $status) {
            throw new BadMethodCallException(sprintf('Request to "%s" failed: %s', $endpoint, $errorMessage));
        }

        return $data;
    }

    /**
     * Parses the content as array.
     *
     * @param string $content
     *
     * @return array
     */
    protected function parseAsArray($content)
    {
        $data = json_decode($content, true);

        return array(
            isset($data['status']) ? $data['status'] : false,
            isset($data['error']) ? $data['error'] : false,
            $data,
        );
    }

    /**
     * Parses the content as objects.
     *
     * @param string $content
     *
     * @return array
     */
    protected function parseAsObject($content)
    {
        $data = json_decode($content);

        return array(
            property_exists($data, 'status') ? $data->status : false,
            property_exists($data, 'error') ? $data->error : false,
            $data
        );
    }

    /**
     * Magic method with maps instance method calls to API methods.
     *
     * @param $method
     * @param $args
     *
     * @return mixed
     * @throws BadMethodCallException
     */
    public function __call($method, $args)
    {
        if (!preg_match('/([a-z]+)([A-Z][a-z_]+)$/', $method, $matches)) {
            throw new BadMethodCallException(sprintf('Invalid endpoint name: %s', $method));
        }

        $result = $this->post(
            strtolower($matches[1] . '/' . str_replace('_', '-', $matches[2])),
            empty($args) ? array() : $args[0]
        );

        return $result;
    }

    /**
     * @return ClientInterface
     */
    protected function getClient()
    {
        return $this->client === null ? new Curl() : $this->client;
    }

    /**
     * Use to override the internal default curl buzz client used for calling the API.
     *
     * @param ClientInterface $client
     *
     * @return self
     */
    public function setClient(ClientInterface $client)
    {
        $this->client = $client;
        return $this;
    }

    /**
     * Sets the return type of responses.
     *
     * @param string $returnType
     *
     * @return self
     * @throws InvalidArgumentException
     */
    public function setReturnType($returnType)
    {
        if (!in_array($returnType, array(static::RETURN_OBJECT, static::RETURN_ARRAY))) {
            throw new InvalidArgumentException(sprintf('Invalid return type "%s" given.', $returnType));
        }

        $this->returnType = $returnType;

        return $this;
    }
}


<?php
namespace Jumpstart\Battery;

/**
 * Register all actions and filters for the plugin.
 *
 * Maintain a list of all hooks that are registered throughout
 * the plugin, and register them with the WordPress API. Call the
 * run function to execute the list of actions and filters.
 *
 * @since       1.0.0
 * @package     Jumpstart\Battery
 * @author      Moritz Kornher <mail@moritzkornher.de>
 */
class Loader
{
    /**
     * The slug of this plugin.
     *
     * @since   1.0.0
     * @access  protected
     * @var     string $slug The slug of this plugin.
     */
    protected $slug;

    /**
     * The version of this plugin.
     *
     * @since   1.0.0
     * @access  protected
     * @var     string $version The current version of this plugin.
     */
    protected $version;

    /**
     * The basename of this plugin.
     *
     * @since   1.0.0
     * @access  protected
     * @var     string $path The basename of this plugin.
     */
    protected $basename;

    /**
     * The array of actions registered with WordPress.
     *
     * @since   1.0.0
     * @access  protected
     * @var     array $actions The actions registered with WordPress to fire when the plugin loads.
     */
    protected $actions;

    /**
     * The array of filters registered with WordPress.
     *
     * @since   1.0.0
     * @access  protected
     * @var     array $filters The filters registered with WordPress to fire when the plugin loads.
     */
    protected $filters;

    /**
     * The array of styles registered with WordPress.
     *
     * @since   1.0.0
     * @access  protected
     * @var     array $styles The styles registered with WordPress to load when the plugin loads.
     */
    protected $styles;

    /**
     * The array of scripts registered with WordPress.
     *
     * @since   1.0.0
     * @access  protected
     * @var     array $scripts The scripts registered with WordPress to load when the plugin loads.
     */
    protected $scripts;

    /**
     * Initialize the collections used to maintain the actions and filters.
     *
     * @since   1.0.0
     * @param   $slug
     * @param   $version
     * @param   $basename
     */
    public function __construct($slug, $version, $basename)
    {
        $this->slug = $slug;
        $this->version = $version;
        $this->basename = $basename;

        $this->actions = array();
        $this->filters = array();
        $this->styles = array();
        $this->scripts = array();
    }

    /**
     * Add a new action to the collection to be registered with WordPress.
     *
     * @since   1.0.0
     * @param   string $hook The name of the WordPress action that is being registered.
     * @param   callable $callback The name of the function definition on the $component.
     * @param   int $priority The priority at which the function should be fired.
     * @param   int $accepted_args The number of arguments that should be passed to the $callback.
     */
    public function action($hook, $callback, $priority = 10, $accepted_args = 1)
    {
        $this->actions = $this->add($this->actions, $hook, $callback, $priority, $accepted_args);
    }

    /**
     * Add a new filter to the collection to be registered with WordPress.
     *
     * @since   1.0.0
     * @param   string $hook The name of the WordPress filter that is being registered.
     * @param   callable $callback The name of the function definition on the $component.
     * @param   int $priority The priority at which the function should be fired.
     * @param   int $accepted_args The number of arguments that should be passed to the $callback.
     */
    public function filter($hook, $callback, $priority = 10, $accepted_args = 1)
    {
        $this->filters = $this->add($this->filters, $hook, $callback, $priority, $accepted_args);
    }

    /**
     * A utility function that is used to register the actions and hooks into a single
     * collection.
     *
     * @since   1.0.0
     * @access  private
     * @param   array $hooks The collection of hooks that is being registered (that is, actions or filters).
     * @param   string $hook The name of the WordPress filter that is being registered.
     * @param   callable $callback The name of the function definition on the $component.
     * @param   int $priority The priority at which the function should be fired.
     * @param   int $accepted_args The number of arguments that should be passed to the $callback.
     * @return  array The collection of actions and filters registered with WordPress.
     */
    protected function add($hooks, $hook, $callback, $priority, $accepted_args)
    {
        $hooks[] = array(
            'hook' => $hook,
            'callback' => $callback,
            'priority' => $priority,
            'accepted_args' => $accepted_args
        );

        return $hooks;
    }

    /**
     * Add a new action to the collection to be registered with WordPress.
     *
     * @since 1.0.0
     * @param bool|string $src Path to the stylesheet from the css directory of the plugin.
     * @param array $deps An array of registered style handles this stylesheet depends on. Default empty array.
     * @param string $type Optional. Include in wp, admin or both.
     * @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts 'all', 'aural', 'braille', 'handheld', 'projection', 'print', 'screen', 'tty', or 'tv'.
     */
    public function style($src = false, $deps = array(), $type = 'both', $media = 'all')
    {
        $this->styles = $this->resource($this->styles, 'assets/css/' . $src, $deps, $type, $media);
    }

    /**
     * Add a new filter to the collection to be registered with WordPress.
     *
     * @since   1.0.0
     * @param   bool|string $src Path to the script from the assets directory of the plugin.
     * @param   array $deps An array of registered handles this script depends on. Default empty array.
     * @param   string $type Optional. Include in wp, admin or both.
     * @param   bool $in_footer Optional. Whether to enqueue the script before or before . Default 'false'. Accepts 'false' or 'true'.
     */
    public function script($src = false, $deps = array(), $type = 'both', $in_footer = false)
    {
        $this->scripts = $this->resource($this->scripts, 'assets/js/' . $src, $deps, $type, $in_footer);
    }

    /**
     * A utility function that is used to register the actions and hooks into a single
     * collection.
     *
     * @since   1.0.0
     * @access  private
     * @param   array $resources The collection of resources that is being registered (that is, styles or scripts).
     * @param   bool|string $src Path to the resource from the root directory of the plugin.
     * @param   array $deps An array of registered handles this resource depends on. Default empty array.
     * @param   string $type Optional. Include in wp, admin or both.
     * @param   bool $cond
     * @return  array The collection of styles and scripts registered with WordPress.
     */
    private function resource($resources, $src, $deps, $type, $cond)
    {
        $resources[] = array(
            'handle' => $this->slug . '_' . $src,
            'src' => plugins_url($src, $this->basename),
            'deps' => $deps,
            'type' => $type,
            'cond' => $cond
        );

        return $resources;
    }

    /**
     * Register the filters and actions with WordPress.
     *
     * @since   1.0.0
     */
    public function run()
    {
        // Enqueue styles and scripts
        $this->action('wp_enqueue_scripts', $this->enqueue('wp'));
        $this->action('admin_enqueue_scripts', $this->enqueue('admin'));

        foreach ($this->filters as $hook) {
            add_filter($hook['hook'], $hook['callback'], $hook['priority'], $hook['accepted_args']);
        }

        foreach ($this->actions as $hook) {
            add_action($hook['hook'], $hook['callback'], $hook['priority'], $hook['accepted_args']);
        }
    }

    /**
     * Create a callable to enqueue resources.
     *
     * @since   1.0.0
     * @access  protected
     * @param   $type
     * @return  callable
     */
    protected function enqueue($type)
    {
        $filter = function ($val) use ($type) {
            return in_array($val['type'], array($type, 'both'));
        };

        return function () use ($filter) {
            foreach (array_filter($this->styles, $filter) as $hook) {
                wp_enqueue_style($hook['handle'], $hook['src'], $hook['deps'], $this->version, $hook['cond']);
            }
            foreach (array_filter($this->scripts, $filter) as $hook) {
                wp_enqueue_script($hook['handle'], $hook['src'], $hook['deps'], $this->version, $hook['cond']);
            }
        };
    }
}


<?php

namespace Think\Image\Driver;

use Think\BaseException;
use Think\Image;
use Think\Image\IImage;

class Imagick implements IImage
{
    /**
     * 图像资源对象
     * @var \Imagick $img
     */
    private $img;

    /**
     * 图像信息，包括width,height,type,mime,size
     * @var array
     */
    private $info;

    /**
     * 构造方法，可用于打开一张图像
     *
     * @param string $imgname 图像路径
     */
    public function __construct($imgname = null)
    {
        $imgname && $this->open($imgname);
    }

    /**
     * 打开一张图像
     *
     * @param  string $imgname 图像路径
     * @throws BaseException
     */
    public function open($imgname)
    {
        //检测图像文件
        if (!is_file($imgname)) {
            throw new BaseException('不存在的图像文件');
        }

        //销毁已存在的图像
        empty($this->img) || $this->img->destroy();

        //载入图像
        $this->img = new \Imagick(realpath($imgname));

        //设置图像信息
        $this->info = [
            'width'  => $this->img->getImageWidth(),
            'height' => $this->img->getImageHeight(),
            'type'   => strtolower($this->img->getImageFormat()),
            'mime'   => $this->img->getImageMimeType(),
        ];
    }

    /**
     * 保存图像
     *
     * @param  string $imgname 图像保存名称
     * @param  string $type 图像类型
     * @param  int $quality JPEG图像质量
     * @param  bool $interlace 是否对JPEG类型图像设置隔行扫描
     * @throws BaseException
     */
    public function save($imgname, $type = null, $quality = 80, $interlace = true)
    {
        if (empty($this->img)) {
            throw new BaseException('没有可以被保存的图像资源');
        }

        //设置图片类型
        if (is_null($type)) {
            $type = $this->info['type'];
        } else {
            $type = strtolower($type);
            $this->img->setImageFormat($type);
        }

        //JPEG图像设置隔行扫描
        if ('jpeg' == $type || 'jpg' == $type) {
            $this->img->setImageInterlaceScheme(1);
        }

        // 设置图像质量
        $this->img->setImageCompressionQuality($quality);

        //去除图像配置信息
        $this->img->stripImage();

        //保存图像
        $imgname = realpath(dirname($imgname)).'/'.basename($imgname); //强制绝对路径
        if ('gif' == $type) {
            $this->img->writeImages($imgname, true);
        } else {
            $this->img->writeImage($imgname);
        }
    }

    /**
     * 返回图像宽度
     * @return int 图像宽度
     * @throws BaseException
     */
    public function width()
    {
        if (empty($this->img)) {
            throw new BaseException('没有指定图像资源');
        }

        return $this->info['width'];
    }

    /**
     * 返回图像高度
     * @return int 图像高度
     * @throws BaseException
     */
    public function height()
    {
        if (empty($this->img)) {
            throw new BaseException('没有指定图像资源');
        }

        return $this->info['height'];
    }

    /**
     * 返回图像类型
     * @return string 图像类型
     * @throws BaseException
     */
    public function type()
    {
        if (empty($this->img)) {
            throw new BaseException('没有指定图像资源');
        }

        return $this->info['type'];
    }

    /**
     * 返回图像MIME类型
     * @return string 图像MIME类型
     * @throws BaseException
     */
    public function mime()
    {
        if (empty($this->img)) {
            throw new BaseException('没有指定图像资源');
        }

        return $this->info['mime'];
    }

    /**
     * 返回图像尺寸数组 0 - 图像宽度，1 - 图像高度
     * @return array 图像尺寸
     * @throws BaseException
     */
    public function size()
    {
        if (empty($this->img)) {
            throw new BaseException('没有指定图像资源');
        }

        return [$this->info['width'], $this->info['height']];
    }

    /**
     * 裁剪图像
     *
     * @param  int $w 裁剪区域宽度
     * @param  int $h 裁剪区域高度
     * @param  int $x 裁剪区域x坐标
     * @param  int $y 裁剪区域y坐标
     * @param  int $width 图像保存宽度
     * @param  int $height 图像保存高度
     * @throws BaseException
     */
    public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null)
    {
        if (empty($this->img)) {
            throw new BaseException('没有可以被裁剪的图像资源');
        }

        //设置保存尺寸
        empty($width) && $width = $w;
        empty($height) && $height = $h;

        //裁剪图片
        if ('gif' == $this->info['type']) {
            $_img = $this->img->coalesceImages();
            $this->img->destroy(); //销毁原图

            //循环裁剪每一帧
            do {
                $this->_crop($w, $h, $x, $y, $width, $height, $_img);
            } while ($_img->nextImage());

            //压缩图片
            $this->img = $_img->deconstructImages();
            $_img->destroy(); //销毁零时图片
        } else {
            $this->_crop($w, $h, $x, $y, $width, $height);
        }
    }

    /**
     * 裁剪图片，内部调用
     *
     * @param      $w
     * @param      $h
     * @param      $x
     * @param      $y
     * @param      $width
     * @param      $height
     * @param null $img
     */
    private function _crop($w, $h, $x, $y, $width, $height, $img = null)
    {
        is_null($img) && $img = $this->img;

        //裁剪
        $_info = $this->info;
        if ($x != 0 || $y != 0 || $w != $_info['width'] || $h != $_info['height']) {
            $img->cropImage($w, $h, $x, $y);
            $img->setImagePage($w, $h, 0, 0); //调整画布和图片一致
        }

        //调整大小
        if ($w != $width || $h != $height) {
            $img->sampleImage($width, $height);
        }

        //设置缓存尺寸
        $this->info['width'] = $width;
        $this->info['height'] = $height;
    }

    /**
     * 生成缩略图
     *
     * @param  int $width 缩略图最大宽度
     * @param  int $height 缩略图最大高度
     * @param int $type 缩略图裁剪类型
     * @throws BaseException
     */
    public function thumb($width, $height, $type = Image::IMAGE_THUMB_SCALE)
    {
        if (empty($this->img)) {
            throw new BaseException('没有可以被缩略的图像资源');
        }

        //原图宽度和高度
        $w = $this->info['width'];
        $h = $this->info['height'];

        /* 计算缩略图生成的必要参数 */
        switch ($type) {
            /* 等比例缩放 */
            case Image::IMAGE_THUMB_SCALE:
                //原图尺寸小于缩略图尺寸则不进行缩略
                if ($w < $width && $h < $height) {
                    return;
                }

                //计算缩放比例
                $scale = min($width / $w, $height / $h);

                //设置缩略图的坐标及宽度和高度
                $x = $y = 0;
                $width = $w * $scale;
                $height = $h * $scale;
                break;

            /* 居中裁剪 */
            case Image::IMAGE_THUMB_CENTER:
                //计算缩放比例
                $scale = max($width / $w, $height / $h);

                //设置缩略图的坐标及宽度和高度
                $w = $width / $scale;
                $h = $height / $scale;
                $x = ($this->info['width'] - $w) / 2;
                $y = ($this->info['height'] - $h) / 2;
                break;

            /* 左上角裁剪 */
            case Image::IMAGE_THUMB_NORTHWEST:
                //计算缩放比例
                $scale = max($width / $w, $height / $h);

                //设置缩略图的坐标及宽度和高度
                $x = $y = 0;
                $w = $width / $scale;
                $h = $height / $scale;
                break;

            /* 右下角裁剪 */
            case Image::IMAGE_THUMB_SOUTHEAST:
                //计算缩放比例
                $scale = max($width / $w, $height / $h);

                //设置缩略图的坐标及宽度和高度
                $w = $width / $scale;
                $h = $height / $scale;
                $x = $this->info['width'] - $w;
                $y = $this->info['height'] - $h;
                break;

            /* 填充 */
            case Image::IMAGE_THUMB_FILLED:
                //计算缩放比例
                if ($w < $width && $h < $height) {
                    $scale = 1;
                } else {
                    $scale = min($width / $w, $height / $h);
                }

                //设置缩略图的坐标及宽度和高度
                $neww = $w * $scale;
                $newh = $h * $scale;
                $posx = ($width - $w * $scale) / 2;
                $posy = ($height - $h * $scale) / 2;

                //创建一张新图像
                $newimg = new \Imagick();
                $newimg->newImage($width, $height, 'white', $this->info['type']);

                if ('gif' == $this->info['type']) {
                    $imgs = $this->img->coalesceImages();
                    $_img = new \Imagick();
                    $this->img->destroy(); //销毁原图

                    //循环填充每一帧
                    do {
                        //填充图像
                        $image = $this->_fill($newimg, $posx, $posy, $neww, $newh, $imgs);

                        $_img->addImage($image);
                        $_img->setImageDelay($imgs->getImageDelay());
                        $_img->setImagePage($width, $height, 0, 0);

                        $image->destroy(); //销毁零时图片

                    } while ($imgs->nextImage());

                    //压缩图片
                    $this->img->destroy();
                    $this->img = $_img->deconstructImages();
                    $imgs->destroy(); //销毁零时图片
                    $_img->destroy(); //销毁零时图片

                } else {
                    //填充图像
                    $_img = $this->_fill($newimg, $posx, $posy, $neww, $newh);
                    //销毁原图
                    $this->img->destroy();
                    $this->img = $_img;
                }

                //设置新图像属性
                $this->info['width'] = $width;
                $this->info['height'] = $height;

                return;

            /* 固定 */
            case Image::IMAGE_THUMB_FIXED:
                $x = $y = 0;
                break;

            default:
                throw new BaseException('不支持的缩略图裁剪类型');
        }

        /* 裁剪图像 */
        $this->crop($w, $h, $x, $y, $width, $height);
    }

    /**
     * 填充指定图像，内部使用
     *
     * @param  \Imagick $newimg
     * @param           $posx
     * @param           $posy
     * @param           $neww
     * @param           $newh
     * @param null $img
     *
     * @return mixed
     */
    private function _fill(\Imagick $newimg, $posx, $posy, $neww, $newh, $img = null)
    {
        is_null($img) && $img = $this->img;

        /* 将指定图片绘入空白图片 */
        $draw = new \ImagickDraw();
        $draw->composite($img->getImageCompose(), $posx, $posy, $neww, $newh, $img);
        $image = $newimg->clone();
        $image->drawImage($draw);
        $draw->destroy();

        return $image;
    }

    /**
     * 添加水印
     *
     * @param  string $source 水印图片路径
     * @param int $locate 水印位置
     * @param  int $alpha 水印透明度
     * @throws BaseException
     */
    public function water($source, $locate = Image::IMAGE_WATER_SOUTHEAST, $alpha = 80)
    {
        //资源检测
        if (empty($this->img)) {
            throw new BaseException('没有可以被添加水印的图像资源');
        }
        if (!is_file($source)) {
            throw new BaseException('水印图像不存在');
        }

        //创建水印图像资源
        $water = new \Imagick(realpath($source));
        $_info = [$water->getImageWidth(), $water->getImageHeight()];

        /* 设定水印位置 */
        switch ($locate) {
            /* 右下角水印 */
            case Image::IMAGE_WATER_SOUTHEAST:
                $x = $this->info['width'] - $_info[0];
                $y = $this->info['height'] - $_info[1];
                break;

            /* 左下角水印 */
            case Image::IMAGE_WATER_SOUTHWEST:
                $x = 0;
                $y = $this->info['height'] - $_info[1];
                break;

            /* 左上角水印 */
            case Image::IMAGE_WATER_NORTHWEST:
                $x = $y = 0;
                break;

            /* 右上角水印 */
            case Image::IMAGE_WATER_NORTHEAST:
                $x = $this->info['width'] - $_info[0];
                $y = 0;
                break;

            /* 居中水印 */
            case Image::IMAGE_WATER_CENTER:
                $x = ($this->info['width'] - $_info[0]) / 2;
                $y = ($this->info['height'] - $_info[1]) / 2;
                break;

            /* 下居中水印 */
            case Image::IMAGE_WATER_SOUTH:
                $x = ($this->info['width'] - $_info[0]) / 2;
                $y = $this->info['height'] - $_info[1];
                break;

            /* 右居中水印 */
            case Image::IMAGE_WATER_EAST:
                $x = $this->info['width'] - $_info[0];
                $y = ($this->info['height'] - $_info[1]) / 2;
                break;

            /* 上居中水印 */
            case Image::IMAGE_WATER_NORTH:
                $x = ($this->info['width'] - $_info[0]) / 2;
                $y = 0;
                break;

            /* 左居中水印 */
            case Image::IMAGE_WATER_WEST:
                $x = 0;
                $y = ($this->info['height'] - $_info[1]) / 2;
                break;

            default:
                /* 自定义水印坐标 */
                if (is_array($locate)) {
                    list($x, $y) = $locate;
                } else {
                    throw new BaseException('不支持的水印位置类型');
                }
        }

        //创建绘图资源
        $draw = new \ImagickDraw();
        $draw->composite($water->getImageCompose(), $x, $y, $_info[0], $_info[1], $water);

        if ('gif' == $this->info['type']) {
            $_img = $this->img->coalesceImages();
            $this->img->destroy(); //销毁原图

            do {
                //添加水印
                $_img->drawImage($draw);
            } while ($_img->nextImage());

            //压缩图片
            $this->img = $_img->deconstructImages();
            $_img->destroy(); //销毁零时图片

        } else {
            //添加水印
            $this->img->drawImage($draw);
        }

        //销毁水印资源
        $draw->destroy();
        $water->destroy();
    }

    /**
     * 图像添加文字
     *
     * @param  string $text 添加的文字
     * @param  string $font 字体路径
     * @param  int $size 字号
     * @param  string $color 文字颜色
     * @param int $locate 文字写入位置
     * @param  int $offset 文字相对当前位置的偏移量
     * @param  int $angle 文字倾斜角度
     * @throws BaseException
     */
    public function text(
        $text,
        $font,
        $size,
        $color = '#00000000',
        $locate = Image::IMAGE_WATER_SOUTHEAST,
        $offset = 0,
        $angle = 0
    ) {
        //资源检测
        if (empty($this->img)) {
            throw new BaseException('没有可以被写入文字的图像资源');
        }
        if (!is_file($font)) {
            throw new BaseException("不存在的字体文件：{$font}");
        }

        //获取颜色和透明度
        if (is_array($color)) {
            $color = array_map('dechex', $color);
            foreach ($color as &$value) {
                $value = str_pad($value, 2, '0', STR_PAD_LEFT);
            }
            $color = '#'.implode('', $color);
        } elseif (!is_string($color) || 0 !== strpos($color, '#')) {
            throw new BaseException('错误的颜色值');
        }
        $col = substr($color, 0, 7);
        $alp = strlen($color) == 9 ? substr($color, -2) : 0;

        //获取文字信息
        $draw = new \ImagickDraw();
        $draw->setFont(realpath($font));
        $draw->setFontSize($size);
        $draw->setFillColor($col);
        $draw->setFillAlpha(1 - hexdec($alp) / 127);
        $draw->setTextAntialias(true);
        $draw->setStrokeAntialias(true);

        $metrics = $this->img->queryFontMetrics($draw, $text);

        /* 计算文字初始坐标和尺寸 */
        $x = 0;
        $y = $metrics['ascender'];
        $w = $metrics['textWidth'];
        $h = $metrics['textHeight'];

        /* 设定文字位置 */
        switch ($locate) {
            /* 右下角文字 */
            case Image::IMAGE_WATER_SOUTHEAST:
                $x += $this->info['width'] - $w;
                $y += $this->info['height'] - $h;
                break;

            /* 左下角文字 */
            case Image::IMAGE_WATER_SOUTHWEST:
                $y += $this->info['height'] - $h;
                break;

            /* 左上角文字 */
            case Image::IMAGE_WATER_NORTHWEST:
                // 起始坐标即为左上角坐标，无需调整
                break;

            /* 右上角文字 */
            case Image::IMAGE_WATER_NORTHEAST:
                $x += $this->info['width'] - $w;
                break;

            /* 居中文字 */
            case Image::IMAGE_WATER_CENTER:
                $x += ($this->info['width'] - $w) / 2;
                $y += ($this->info['height'] - $h) / 2;
                break;

            /* 下居中文字 */
            case Image::IMAGE_WATER_SOUTH:
                $x += ($this->info['width'] - $w) / 2;
                $y += $this->info['height'] - $h;
                break;

            /* 右居中文字 */
            case Image::IMAGE_WATER_EAST:
                $x += $this->info['width'] - $w;
                $y += ($this->info['height'] - $h) / 2;
                break;

            /* 上居中文字 */
            case Image::IMAGE_WATER_NORTH:
                $x += ($this->info['width'] - $w) / 2;
                break;

            /* 左居中文字 */
            case Image::IMAGE_WATER_WEST:
                $y += ($this->info['height'] - $h) / 2;
                break;

            default:
                /* 自定义文字坐标 */
                if (is_array($locate)) {
                    list($posx, $posy) = $locate;
                    $x += $posx;
                    $y += $posy;
                } else {
                    throw new BaseException('不支持的文字位置类型');
                }
        }

        /* 设置偏移量 */
        if (is_array($offset)) {
            $offset = array_map('intval', $offset);
            list($ox, $oy) = $offset;
        } else {
            $offset = intval($offset);
            $ox = $oy = $offset;
        }

        /* 写入文字 */
        if ('gif' == $this->info['type']) {
            $_img = $this->img->coalesceImages();
            $this->img->destroy(); //销毁原图
            do {
                $_img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text);
            } while ($_img->nextImage());

            //压缩图片
            $this->img = $_img->deconstructImages();
            $_img->destroy(); //销毁零时图片

        } else {
            $this->img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text);
        }
        $draw->destroy();
    }

    /**
     * 析构方法，用于销毁图像资源
     */
    public function __destruct()
    {
        empty($this->img) || $this->img->destroy();
    }
}


<?php

declare(strict_types=1);

namespace Psi\Component\Description;

use Psi\Component\Description\Schema\Schema;

/**
 * Decorator for description which validates the descriptions based
 * on the given Schema.
 *
 * Note that for performance reasons this should only be used in a development
 * environemnt.
 */
class ValidatedDescription implements DescriptionInterface
{
    /**
     * @var Description
     */
    private $description;

    /**
     * @var Scehma
     */
    private $schema;

    public function __construct(DescriptionInterface $description, Schema $schema)
    {
        $this->description = $description;
        $this->schema = $schema;
    }

    /**
     * {@inheritdoc}
     */
    public function get($descriptorKey): DescriptorInterface
    {
        $this->schema->validateKey($descriptorKey);

        return $this->description->get($descriptorKey);
    }

    /**
     * {@inheritdoc}
     */
    public function has($descriptor): bool
    {
        $this->schema->validateKey($descriptor);

        return $this->description->has($descriptor);
    }

    /**
     * {@inheritdoc}
     */
    public function all(): array
    {
        return $this->description->all();
    }

    /**
     * {@inheritdoc}
     */
    public function set(string $key, DescriptorInterface $descriptor, int $priority = 0)
    {
        $this->schema->validate($key, $descriptor);
        $this->description->set($key, $descriptor, $priority);
    }
}


<?php
/**
 * Поле типа «integer»
 *
 * @version ${product.version}
 *
 * @copyright 2013, Михаил Красильников <m.krasilnikov@yandex.ru>
 * @license http://www.gnu.org/licenses/gpl.txt	GPL License 3
 * @author Михаил Красильников <m.krasilnikov@yandex.ru>
 *
 * Данная программа является свободным программным обеспечением. Вы
 * вправе распространять ее и/или модифицировать в соответствии с
 * условиями версии 3 либо (по вашему выбору) с условиями более поздней
 * версии Стандартной Общественной Лицензии GNU, опубликованной Free
 * Software Foundation.
 *
 * Мы распространяем эту программу в надежде на то, что она будет вам
 * полезной, однако НЕ ПРЕДОСТАВЛЯЕМ НА НЕЕ НИКАКИХ ГАРАНТИЙ, в том
 * числе ГАРАНТИИ ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ и ПРИГОДНОСТИ ДЛЯ
 * ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ. Для получения более подробной
 * информации ознакомьтесь со Стандартной Общественной Лицензией GNU.
 *
 * Вы должны были получить копию Стандартной Общественной Лицензии
 * GNU с этой программой. Если Вы ее не получили, смотрите документ на
 * <http://www.gnu.org/licenses/>
 *
 * @package ORM
 */

/**
 * Поле типа «integer»
 *
 * @package ORM
 * @since 3.00
 */
class ORM_Field_Integer extends ORM_Field_Abstract
{
    /**
     * Возвращает имя типа
     *
     * @return string
     *
     * @since 3.00
     */
    public function getTypeName()
    {
        return 'integer';
    }

    /**
     * Возвращает соответствующий тип PDO (PDO::PARAM_…) или null
     *
     * @return null|int
     *
     * @since 3.00
     */
    public function getPdoType()
    {
        return PDO::PARAM_INT;
    }

    /**
     * Действия, выполняемые после сохранения сущности
     *
     * @param ORM_Entity $entity
     */
    public function afterEntitySave(ORM_Entity $entity)
    {
        if ($entity->getEntityState() == ORM_Entity::IS_NEW && $this->getParam('autoincrement'))
        {
            $entity->{$this->getName()} = Eresus_DB::getHandler()->lastInsertId();
        }
    }

    /**
     * Возвращает выражение SQL для описания поля при создании таблицы
     *
     * @return string
     */
    public function getSqlFieldDefinition()
    {
        $sql = $this->getName() . ' INT';
        $length = $this->getParam('length') ? $this->getParam('length') : 10;
        $sql .= '(' . $length . ')';
        if ($this->getParam('unsigned'))
        {
            $sql .= ' UNSIGNED';
        }
        if ($this->getParam('autoincrement'))
        {
            $sql .= ' AUTO_INCREMENT';
        }
        return $sql;
    }

    /**
     * Возвращает список возможных необязательных параметров
     *
     * @return string[]
     *
     * @since 3.00
     */
    protected function getOptionalParams()
    {
        return array('autoincrement', 'length', 'unsigned');
    }
}


<?php

namespace Yami\SheetFight\Model;

use LogicException;

/**
 * Provides a fluent interface to build a MoveInterface.
 *
 * @author Kevin GITTENS <kgittens973@gmail.com>
 * @author Ludovic FLEURY <ludo.fleury@gmail.com>
 */
class MoveBuilder
{
    private $parser;
    private $type;
    private $name;
    private $initialPosition;
    private $inputs;
    private $damage;
    private $meterGain;
    private $hitLevel;
    private $cancelAbilities;
    private $startFrames;
    private $activeFrames;
    private $recoveryFrames;
    private $guardAdvantage;
    private $hitAdvantage;

    public function __construct(InputParserInterface $parser)
    {
        $this->parser = $parser;
        $this->inputs = [];
        $this->cancelAbilities = [];

        return $this;
    }

    /**
     * Define the type.
     *
     * @param string $type
     *
     * @return static
     */
    public function is($type)
    {
        if (null !== $this->type) {
            throw new LogicException('Already typed');
        }

        $this->type = $type;

        return $this;
    }

    /**
     * Define the name.
     *
     * @param string $name
     *
     * @return static
     */
    public function named($name)
    {
        if (null !== $this->name) {
            throw new LogicException('Already named');
        }

        $this->name = $name;

        return $this;
    }

    /**
     * Define the position.
     *
     * @param string $position
     *
     * @return static
     */
    public function start($position)
    {
        if (null !== $this->initialPosition) {
            throw new LogicException('Start already defined');
        }

        $this->initialPosition = $position;

        return $this;
    }

    /**
     * Define the damage.
     *
     * @param int $damage
     *
     * @return static
     */
    public function deal($damage)
    {
        if (null !== $this->damage) {
            throw new LogicException('Damage already defined');
        }

        $this->damage = $damage;

        return $this;
    }

    /**
     * Define the hit level.
     *
     * @param string $hitLevel
     *
     * @return static
     */
    public function aim($hitLevel)
    {
        if (null !== $this->hitLevel) {
            throw new LogicException('Hit level already defined');
        }

        $this->hitLevel = $hitLevel;

        return $this;
    }

    /**
     * Define the meter gain.
     *
     * @param int $value
     *
     * @return static
     */
    public function withMeterGain($value)
    {
        if (null !== $this->meterGain) {
            throw new LogicException('Meter gain already defined');
        }

        $this->meterGain = $value;

        return $this;
    }

    /**
     * Define the inputs.
     *
     * @param string $inputs
     *
     * @return static
     */
    public function withInputs($inputs)
    {
        if (0 < count($this->inputs)) {
            throw new LogicException('Inputs already defined');
        }

        $this->inputs = $this->parser->transforms($inputs);

        return $this;
    }

    /**
     * Add a cancel ability.
     *
     * @param MoveInterface $move
     *
     * @return static
     */
    public function addCancel(MoveInterface $move)
    {
        foreach ($this->cancelAbilities as $cancelAbility) {
            if ($cancelAbility->equals($move)) {
                throw new LogicException(
                    sprintf('Cancel ability "%s" already defined', $move->getName())
                );
            }
        }

        $this->cancelAbilities[] = $move;

        return $this;
    }

    /**
     * Define the frames required to start.
     *
     * @param int $frames
     *
     * @return static
     */
    public function startIn($frames)
    {
        if (null !== $this->startFrames) {
            throw new LogicException('Start frames already defined');
        }
        $this->startFrames = $frames;

        return $this;
    }

    /**
     * Define the frames displaying the move.
     *
     * @param int $frames
     *
     * @return static
     */
    public function activeDuring($frames)
    {
        if (null !== $this->activeFrames) {
            throw new LogicException('Active frames already defined');
        }
        $this->activeFrames = $frames;

        return $this;
    }

    /**
     * Define the frames required to recover.
     *
     * @param int $frames
     *
     * @return static
     */
    public function recoverIn($frames)
    {
        if (null !== $this->recoveryFrames) {
            throw new LogicException('Recovery frames already defined');
        }
        $this->recoveryFrames = $frames;

        return $this;
    }

    /**
     * Define the difference of frame with the target gets hit to get back to the neutral state.
     *
     * @param int $frames
     *
     * @return static
     */
    public function advantageOnHit($frames)
    {
        if (null !== $this->hitAdvantage) {
            throw new LogicException('Hit advantage already defined');
        }
        $this->hitAdvantage = $frames;

        return $this;
    }

    /**
     * Define the difference of frame with the target on guard to get back to the neutral state.
     *
     * @param int $frames
     *
     * @return static
     */
    public function advantageOnGuard($frames)
    {
        if (null !== $this->guardAdvantage) {
            throw new LogicException('Guard advantage already defined');
        }
        $this->guardAdvantage = $frames;

        return $this;
    }

    /**
     * Build the desired move.
     *
     * @return MoveInterface
     */
    public function build()
    {
        return new Move(
            $this->type,
            $this->name,
            $this->initialPosition,
            $this->inputs,
            $this->damage,
            $this->meterGain,
            $this->hitLevel,
            $this->cancelAbilities,
            new FrameData(
                $this->startFrames,
                $this->activeFrames,
                $this->recoveryFrames,
                $this->guardAdvantage,
                $this->hitAdvantage
            )
        );
    }
}


<?php
namespace Suilven\Notifier;

/**
 * Created by PhpStorm.
 * User: gordon
 * Date: 16/3/2561
 * Time: 0:02 น.
 */

use Maknz\Slack\Client;
use Psr\Log\LoggerInterface;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use Suilven\Notifier\Jobs\NotifyViaSlackJob;
use Symbiote\QueuedJobs\Services\QueuedJobService;

class Notify
{
    const LEVELS = [
      'DEBUG' => 1,
      'INFO' => 2,
      'NOTICE' => 3,
      'WARNING' => 4,
      'ERROR' => 5,
      'CRITICAL' => 6,
      'ALERT' => 7,
      'EMERGENCY' => 8,
    ];

    /**
     * @var bool|string if this is set to a channel name, the channel will be set to this value.  This allows likes of
     *      all dev Slack activity to go to one channel, but for them to be separated in production
     */
    private static $channel_override = false;

    /**
     * Send a debug message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function debug($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send an info message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function info($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send a notice message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function notice($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send a warning message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function warning($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send an error message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function error($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send a critical message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function critical($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send an alert message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function alert($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Send an emergency message to slack
     *
     * @param $message message to send
     * @param string $channel channel to ping
     */
    public static function emergency($message, $channel = 'development')
    {
        self::sendSlackMessage($message, $channel);
    }

    /**
     * Note to self - Slack Webhook URLs have a default channel, but can be configured to send to any
     * BackTrace class has an error generator
     *
     * Use $client->to($channel)->send($message);
     *
     * @param $message
     * @param $channel
     */
    private static function sendSlackMessage($message, $channel)
    {
        // do not send Slack messages in test mode for now
        // @todo Trap these for testing
        if (Director::isTest()) {
            return;
        }

        $hooksFromConfig = Config::inst()->get('Suilven\Notifier\Notify', 'slack_webhooks');

        // optionally override the channel
        $channelOverride = Config::inst()->get('Suilven\Notifier\Notify', 'channel_override');
        if (!empty($channelOverride)) {
            $channel = $channelOverride;
        }

        // get the appropriate webhook
        $url = null;

        if (!empty($hooksFromConfig)) {
            foreach($hooksFromConfig as $hook)
            {
                // stick with the default webhook but allow overriding on a per channel basis
                if ($hook['name'] == $channel)
                {
                    $url = $hook['url'];
                } elseif (empty($url) && $hook['name'] == 'default') {
                    $url = $hook['url'];
                }
            }
        }

        // Log an error if Slack config misconfigured
        if (empty($url)) {
            Injector::inst()->get(LoggerInterface::class)->error('The slack channel ' . $channel . ' has no webhook');
            return;
        }

        // create job
        $job = new NotifyViaSlackJob();
        $job->webhookURL = $url;
        $job->message = $message;
        $job->channel = $channel;

        // place on queue
        singleton(QueuedJobService::class)->queueJob($job);
    }
}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\Path;

use InvalidArgumentException;
use Valkyrja\Application;
use Valkyrja\Support\Providers\Provides;

/**
 * Class PathGenerator.
 *
 * @author Melech Mizrachi
 */
class NativePathGenerator implements PathGenerator
{
    use Provides;

    /**
     * Parse segments, data, and params into a path.
     *
     * @param array $segments The segments
     * @param array $data     [optional] The data
     * @param array $params   [optional] The params
     *
     * @throws \InvalidArgumentException
     *
     * @return string
     */
    public function parse(array $segments, array $data = null, array $params = null): string
    {
        // If data was passed but no params
        if (null === $params && null !== $data) {
            throw new InvalidArgumentException(
                'Route params are required when supplying data'
            );
        }

        $path         = '';
        $replace      = [];
        $replacements = [];

        // If there is data, parse the replacements
        if (null !== $data) {
            $this->parseData(
                $segments,
                $data,
                $params,
                $replace,
                $replacements
            );
        }

        // Iterate through the segments
        foreach ($segments as $index => $segment) {
            // No need to do replacements if there was no data
            if (null !== $data) {
                // Replace any parameters
                $segment = str_replace($replace, $replacements, $segment);
            }

            // If parameters were replaced or none to begin with
            if (strpos($segment, '{') === false) {
                // Append this segment
                $path .= $segment;
            }
        }

        return $path;
    }

    /**
     * Parse data for replacements.
     *
     * @param array $segments     The segments
     * @param array $data         The data
     * @param array $params       The params
     * @param array $replace      The replace array
     * @param array $replacements The replacements array
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    protected function parseData(
        array $segments,
        array $data,
        array $params,
        array &$replace,
        array &$replacements
    ): void {
        // Iterate through all the data properties
        foreach ($data as $key => $datum) {
            // If the data isn't found in the params array it is not a valid
            // param
            if (! isset($params[$key])) {
                throw new InvalidArgumentException(
                    "Invalid route param '{$key}'"
                );
            }

            $regex = $params[$key]['regex'];

            $this->validateDatum($key, $datum, $regex);

            if (\is_array($datum)) {
                // Get the segment by the param key and replace the {key}
                // within it to get the repeatable portion of the segment
                $segment     = $this->findParamSegment(
                    $segments,
                    $params[$key]['replace']
                );
                $deliminator = str_replace(
                    $params[$key]['replace'] . '*',
                    '',
                    $segment
                );

                // Set what to replace
                $replace[] = $params[$key]['replace'] . '*';
                // With the data value to replace with
                $replacements[] = implode($deliminator, $datum);

                continue;
            }

            // Set what to replace
            $replace[] = $params[$key]['replace'];
            // With the data value to replace with
            $replacements[] = $datum;
        }
    }

    /**
     * Validate a datum.
     *
     * @param string $key   The key
     * @param mixed  $datum The datum
     * @param string $regex The regex
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    protected function validateDatum(string $key, $datum, string $regex): void
    {
        if (\is_array($datum)) {
            foreach ($datum as $datumItem) {
                $this->validateDatum($key, $datumItem, $regex);
            }

            return;
        }

        // If the value of the data doesn't match what was specified when the route was made
        if (preg_match('/^' . $regex . '$/', $datum) === 0) {
            throw new InvalidArgumentException(
                "Route param for {$key}, '{$datum}', does not match {$regex}"
            );
        }
    }

    /**
     * Find a segment with a param key.
     *
     * @param array  $segments
     * @param string $param
     *
     * @return string
     */
    protected function findParamSegment(array $segments, string $param): ? string
    {
        $segment = null;

        foreach ($segments as $segment) {
            if (strpos($segment, $param) !== false) {
                return $segment;
            }
        }

        return $segment;
    }

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

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


<?php namespace Way\Generators\Commands;

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

class ResourceGeneratorCommand extends Command {

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

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

    /**
     * Generate a resource
     *
     * @return mixed
     */
    public function fire()
    {
        $resource = $this->argument('resource');

        $this->callModel($resource);
        $this->callRepository($resource);
        $this->callView($resource);
        $this->callController($resource);
        $this->callMigration($resource);
        $this->callSeeder($resource);
        $this->callMigrate();

        // All done!
        $this->info(sprintf(
            "All done! Don't forget to add '%s` to %s." . PHP_EOL,
            "Route::resource('{$this->getTableName($resource)}', '{$this->getControllerName($resource)}');",
            "app/router.php"
        ));

    }

    /**
     * Get the name for the model
     *
     * @param $resource
     * @return string
     */
    protected function getModelName($resource)
    {
        return ucwords(str_singular(camel_case($resource)));
    }

    /**
     * Get the name for the repository
     *
     * @param $resource
     * @return string
     */
    protected function getRepositoryName($resource)
    {
        return ucwords(str_plural(camel_case($resource))). 'Repository';
    }

    /**
     * Get the name for the controller
     *
     * @param $resource
     * @return string
     */
    protected function getControllerName($resource)
    {
        return ucwords(str_plural(camel_case($resource))) . 'Controller';
    }

    /**
     * Get the DB table name
     *
     * @param $resource
     * @return string
     */
    protected function getTableName($resource)
    {
        return str_plural($resource);
    }

    /**
     * Get the name for the migration
     *
     * @param $resource
     * @return string
     */
    protected function getMigrationName($resource)
    {
        return "create_" . str_plural($resource) . "_table";
    }

    /**
     * Call model generator if user confirms
     *
     * @param $resource
     */
    protected function callModel($resource)
    {
        $modelName = $this->getModelName($resource);

        if ($this->confirm("Do you want me to create a $modelName model? [yes|no]"))
        {
            $this->call('generate:model', compact('modelName'));
        }
    }

    /**
     * Call Repository generator if user confirms
     *
     * @param $resource
     */
    protected function callRepository($resource)
    {
        $repositoryName = $this->getRepositoryName($resource);

        if ($this->confirm("Do you want me to create a $repositoryName Repository? [yes|no]"))
        {
            $this->call('generate:repository', compact('repositoryName'));
        }
    }

    /**
     * Call view generator if user confirms
     *
     * @param $resource
     */
    protected function callView($resource)
    {
        $collection = $this->getTableName($resource);
        $modelName = $this->getModelName($resource);

        if ($this->confirm("Do you want me to create views for this $modelName resource? [yes|no]"))
        {
            foreach(['index', 'show', 'create', 'edit'] as $viewName)
            {
                $viewName = "{$collection}.{$viewName}";

                $this->call('generate:view', compact('viewName'));
            }
        }
    }

    /**
     * Call controller generator if user confirms
     *
     * @param $resource
     */
    protected function callController($resource)
    {
        $controllerName = $this->getControllerName($resource);

        if ($this->confirm("Do you want me to create a $controllerName controller? [yes|no]"))
        {
            $this->call('generate:controller', compact('controllerName'));
        }
    }

    /**
     * Call migration generator if user confirms
     *
     * @param $resource
     */
    protected function callMigration($resource)
    {
        $migrationName = $this->getMigrationName($resource);

        if ($this->confirm("Do you want me to create a '$migrationName' migration and schema for this resource? [yes|no]"))
        {
            $this->call('generate:migration', [
                'migrationName' => $migrationName,
                '--fields' => $this->option('fields')
            ]);
        }
    }

    /**
     * Call seeder generator if user confirms
     *
     * @param $resource
     */
    protected function callSeeder($resource)
    {
        $tableName = str_plural($this->getModelName($resource));

        if ($this->confirm("Would you like a '$tableName' table seeder? [yes|no]"))
        {
            $this->call('generate:seed', compact('tableName'));
        }
    }

    /**
     * Migrate database if user confirms
     */
    protected function callMigrate()
    {
        if ($this->confirm('Do you want to go ahead and migrate the database? [yes|no]')) {
            $this->call('migrate');
            $this->info('Done!');
        }
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['resource', InputArgument::REQUIRED, 'Singular resource name']
        ];
    }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
            ['fields', null, InputOption::VALUE_OPTIONAL, 'Fields for the migration']
        ];
    }

}


<?php

/**
 * Jin Framework
 * Diatem
 */

namespace Jin2\Form\Components;

/**
 * Composant <input type="date">
 */
class InputDate extends AbstractComponent
{

  /**
   * Implémente la fonction AbstractComponent::getType()
   *
   * @return string
   */
  public static function getType()
  {
    return 'inputdate';
  }

  /**
   * Surcharger la méthode AbstracComponent::getValue() pour formatter la date
   *
   * @return srintg
   */
  public function getValue()
  {
    $value = parent::getValue();

    if (is_null($value) || empty($value)) {
      return null;
    }

    if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $value)) {
      return $value;
    }

    $dt = new \DateTime($value);
    return $dt->format('d/m/Y');
  }

}


<?php declare(strict_types = 1); namespace BapCat\Values;

use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;

/**
 * Represents a class
 *
 * @property-read  string  $raw
 *
 * @author    Corey Frenette
 * @copyright Copyright (c) 2019, BapCat
 */
class ClassName extends Value {
  /** @var  string  $name  The name of the class */
  private $name;

  /**
   * @param  string  $name  The raw class name to wrap
   */
  public function __construct($name) {
    $this->validate($name);

    $this->name = $name;
  }

  /**
   * Ensures the name passed in belongs to a valid class
   *
   * @throws  InvalidArgumentException  If the value is not a valid class
   *
   * @param  string  $name  The value to validate
   *
   * @return  void
   */
  private function validate($name): void {
    try {
      new ReflectionClass($name);
    } catch(ReflectionException $ex) {
      throw new InvalidArgumentException('Expected class name, but got [' . var_export($name, true) . '] instead');
    }
  }

  /**
   * Converts this object to a string
   *
   * @return  string  A string representation of this object
   */
  public function __toString(): string {
    return $this->name;
  }

  /**
   * Converts this object to a json encodable-form
   *
   * @return  string  A representation of this object suitable for encoding
   */
  public function jsonSerialize(): string {
    return $this->name;
  }

  /**
   * Gets the raw value this object wraps
   *
   * @return  string  The raw value this object wraps
   */
  protected function getRaw(): string {
    return $this->name;
  }

  /**
   * Creates a ReflectionClass for this class
   *
   * @throws  ReflectionException
   *
   * @return  ReflectionClass  An instance of ReflectionClass for this class
   */
  public function reflect(): ReflectionClass {
    return new ReflectionClass($this->name);
  }
}