<?php
/**
 * This source file is part of Xloit project.
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the MIT License that is bundled with this package in the file LICENSE.
 * It is also available through the world-wide-web at this URL:
 * <http://www.opensource.org/licenses/mit-license.php>
 * If you did not receive a copy of the license and are unable to obtain it through the world-wide-web,
 * please send an email to <license@xloit.com> so we can send you a copy immediately.
 *
 * @license   MIT
 * @link      http://xloit.com
 * @copyright Copyright (c) 2016, Xloit. All rights reserved.
 */

namespace Xloit\Bridge\Doctrine\ORM;

use DateTime;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Util\Inflector;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query as DoctrineQuery;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\QueryBuilder;
use ReflectionClass;

/**
 * An {@link EntityQueryBuilder} class
 *
 * @package Xloit\Bridge\Doctrine\ORM
 *
 * @method EntityQueryBuilder select($select = null)
 * @method EntityQueryBuilder addSelect($select = null)
 * @method EntityQueryBuilder distinct($flag = true)
 * @method EntityQueryBuilder indexBy($alias, $indexBy)
 * @method EntityQueryBuilder join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
 * @method EntityQueryBuilder innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
 * @method EntityQueryBuilder leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
 * @method EntityQueryBuilder where($predicates)
 * @method EntityQueryBuilder andWhere($where)
 * @method EntityQueryBuilder orWhere($where)
 * @method EntityQueryBuilder having($having)
 * @method EntityQueryBuilder andHaving($having)
 * @method EntityQueryBuilder orHaving($having)
 * @method EntityQueryBuilder addCriteria(Criteria $criteria)
 */
class EntityQueryBuilder extends QueryBuilder
{
    /**
     *
     *
     * @var EntityRepositoryInterface
     */
    protected $repository;

    /**
     *
     *
     * @var Query\Expr
     */
    protected $expr;

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

    /**
     *
     *
     * @var string[]
     */
    private $fieldNames;

    /**
     * Constructor to prevent {@link EntityQueryBuilder} from being loaded more than once.
     *
     * @param EntityManager             $em
     * @param EntityRepositoryInterface $repository
     */
    public function __construct(EntityManager $em, EntityRepositoryInterface $repository)
    {
        parent::__construct($em);

        $this->setRepository($repository);
    }

    /**
     * Configure query to work with repository entity.
     *
     * @param string $alias
     * @param string $indexBy The index for the from.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function selectFromRepositoryEntity($alias = null, $indexBy = null)
    {
        if ($alias) {
            $this->entityAlias = $alias;
        }

        if (!$alias) {
            $alias = $this->getEntityAlias();
        }

        $this->select($alias)->from($this->getEntityClassName(), $alias, $indexBy);

        return $this;
    }

    /**
     *
     *
     * @return EntityRepositoryInterface
     */
    public function getRepository()
    {
        return $this->repository;
    }

    /**
     *
     *
     * @param EntityRepositoryInterface $repository
     *
     * @return $this This QueryBuilder instance.
     */
    public function setRepository(EntityRepositoryInterface $repository)
    {
        $this->repository = $repository;

        return $this;
    }

    /**
     *
     *
     * @return string[]
     */
    public function getFieldNames()
    {
        if (!$this->fieldNames) {
            $classMetadata    = $this->getEntityManager()->getClassMetadata($this->getEntityClassName());
            $this->fieldNames = array_merge($classMetadata->getFieldNames(), $classMetadata->getAssociationNames());
        }

        return $this->fieldNames;
    }

    /**
     * Get related entity alias used in query. Will be used entity class name without namespace
     *
     * @return string
     * @throws \ReflectionException
     */
    public function getEntityAlias()
    {
        if (!$this->entityAlias) {
            $className       = $this->getEntityClassName();
            $reflectionCLass = new ReflectionClass($className);

            if (is_string($reflectionCLass->getConstant('ALIAS_NAME'))) {
                $this->entityAlias = $reflectionCLass->getConstant('ALIAS_NAME');
            } else {
                if (method_exists($this->getRepository(), 'getEntityAlias')) {
                    $this->entityAlias = $this->getRepository()->getEntityAlias();
                } else {
                    $this->entityAlias = end(explode('\\', $className));
                }
            }

            $this->entityAlias = str_replace('.', '', $this->entityAlias);
        }

        return $this->entityAlias;
    }

    /**
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
     * This producer method is intended for convenient inline usage. Example:
     *
     *
     *     $qb = $em->createQueryBuilder();
     *     $qb
     *         ->select('u')
     *         ->from('User', 'u')
     *         ->where($qb->expr()->eq('u.id', 1));
     *
     *
     * For more complex expression construction, consider storing the expression
     * builder object in a local variable.
     *
     * @return Query\Expr
     */
    public function expr()
    {
        if (!($this->expr instanceof Query\Expr)) {
            $this->expr = new Query\Expr;
        }

        return $this->expr;
    }

    /**
     * Turns the query being built into a bulk delete query that ranges over
     * a certain entity type.
     *
     *
     *     $qb = $em->createQueryBuilder()
     *         ->delete('User', 'u')
     *         ->where('u.id = :user_id')
     *         ->setParameter('user_id', 1);
     *
     *
     * @param string $delete The class/type whose instances are subject to the deletion.
     * @param string $alias  The class/type alias used in the constructed query.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function delete($delete = null, $alias = null)
    {
        if ($delete === null) {
            $delete = $this->getEntityClassName();

            if ($alias === null) {
                $alias = $this->getEntityAlias();
            }
        }

        parent::delete($delete, $alias);

        return $this;
    }

    /**
     * Turns the query being built into a bulk update query that ranges over
     * a certain entity type.
     *
     *
     *     $qb = $em->createQueryBuilder()
     *         ->update('User', 'u')
     *         ->set('u.password', md5('password'))
     *         ->where('u.id = ?');
     *
     *
     * @param string $update The class/type whose instances are subject to the update.
     * @param string $alias  The class/type alias used in the constructed query.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function update($update = null, $alias = null)
    {
        if ($update === null) {
            $update = $this->getEntityClassName();

            if ($alias === null) {
                $alias = $this->getEntityAlias();
            }
        }

        parent::update($update, $alias);

        return $this;
    }

    /**
     * Creates and adds a query root corresponding to the entity identified by the given alias,
     * forming a cartesian product with any existing query roots.
     *
     *
     *     $qb = $em->createQueryBuilder()
     *         ->select('u')
     *         ->from('User', 'u');
     *
     *
     * @param string $from    The class name.
     * @param string $alias   The alias of the class.
     * @param string $indexBy The index for the from.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function from($from = null, $alias = null, $indexBy = null)
    {
        if ($from === null) {
            $from = $this->getEntityClassName();

            if ($alias === null) {
                $alias = $this->getEntityAlias();
            }
        }

        parent::from($from, $alias, $indexBy = null);

        return $this;
    }

    /**
     * Sets a new value for a field in a bulk update query.
     *
     *
     *     $qb = $em->createQueryBuilder()
     *         ->update('User', 'u')
     *         ->set('u.password', md5('password'))
     *         ->where('u.id = ?');
     *
     *
     * @param string $key   The key/field to set.
     * @param string $value The value, expression, placeholder, etc.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function set($key, $value)
    {
        parent::set($this->alias($key), $value);

        return $this;
    }

    /**
     * Specifies a grouping over the results of the query.
     * Replaces any previously specified groupings, if any.
     *
     *
     *     $qb = $em->createQueryBuilder()
     *         ->select('u')
     *         ->from('User', 'u')
     *         ->groupBy('u.id');
     *
     *
     * @param string $groupBy The grouping expression.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function groupBy($groupBy)
    {
        /** @var array $groupFields */
        $groupFields = func_get_args();

        if (count($groupFields) > 0) {
            parent::groupBy($this->alias(array_shift($groupFields)));

            foreach ($groupFields as $group) {
                $this->addGroupBy($group);
            }
        }

        return $this;
    }

    /**
     * Adds a grouping expression to the query.
     *
     *
     *     $qb = $em->createQueryBuilder()
     *         ->select('u')
     *         ->from('User', 'u')
     *         ->groupBy('u.lastLogin')
     *         ->addGroupBy('u.createdAt');
     *
     *
     * @param string $groupBy The grouping expression.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function addGroupBy($groupBy)
    {
        /** @var array $groupFields */
        $groupFields = func_get_args();

        foreach ($groupFields as $group) {
            parent::addGroupBy($this->alias($group));
        }

        return $this;
    }

    /**
     * Specifies an ordering for the query results.
     * Replaces any previously specified orderings, if any.
     *
     * @param string $sort  The ordering expression.
     * @param string $order The ordering direction.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function orderBy($sort, $order = null)
    {
        if (is_string($sort)) {
            $sort = $this->alias($sort);
        }

        parent::orderBy($sort, $order);

        return $this;
    }

    /**
     * Adds an ordering to the query results.
     *
     * @param string $sort  The ordering expression.
     * @param string $order The ordering direction.
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function addOrderBy($sort, $order = null)
    {
        if (is_string($sort)) {
            $sort = $this->alias($sort);
        }

        parent::addOrderBy($sort, $order);

        return $this;
    }

    /**
     * Limit max number of results
     *
     * @param int $maxResults
     * @param int $offset
     *
     * @return $this This QueryBuilder instance.
     * @throws Exception\InvalidArgumentException
     */
    public function limit($maxResults, $offset = null)
    {
        if (!is_int($maxResults)) {
            throw new Exception\InvalidArgumentException('Incorrect argument $maxResults. Only number allowed.');
        }

        if ($maxResults < 1) {
            throw new Exception\InvalidArgumentException(
                sprintf('Incorrect maximum results: %d. Only positive number allowed.', $maxResults)
            );
        }

        $this->setMaxResults($maxResults);

        if ($offset !== null) {
            if (!is_int($offset)) {
                throw new Exception\InvalidArgumentException('Incorrect argument $offset. Only number allowed.');
            }

            if ($offset < 0) {
                throw new Exception\InvalidArgumentException(
                    sprintf('Incorrect offset: %d. Only positive number allowed.', $offset)
                );
            }

            $this->setFirstResult($offset);
        }

        return $this;
    }

    /**
     *
     *
     * @param string $column
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function orderAsc($column)
    {
        return $this->orderBy($column, 'ASC');
    }

    /**
     *
     *
     * @param string $column
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function orderDesc($column)
    {
        return $this->orderBy($column, 'DESC');
    }

    /**
     *
     *
     * @param string $column
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function addOrderAscBy($column)
    {
        return $this->addOrderBy($column, 'ASC');
    }

    /**
     *
     *
     * @param string $column
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    public function addOrderDescBy($column)
    {
        return $this->addOrderBy($column, 'DESC');
    }

    /**
     * Limit results with selected page.
     *
     * @param int $page
     * @param int $itemsPerPage
     *
     * @return $this This QueryBuilder instance.
     * @throws \Xloit\Bridge\Doctrine\ORM\Exception\InvalidArgumentException
     */
    public function paginate($page = 1, $itemsPerPage = 10)
    {
        if (!is_int($page) || !is_int($itemsPerPage)) {
            throw new Exception\InvalidArgumentException(
                sprintf(
                    'Incorrect argument %s. Only number allowed.', is_int($page) ? '$itemsPerPage' : '$page'
                )
            );
        }

        if ($page < 1) {
            throw new Exception\InvalidArgumentException(
                sprintf('Incorrect page number: %d. Only positive number allowed.', $page)
            );
        }

        if ($itemsPerPage < 1) {
            throw new Exception\InvalidArgumentException(
                sprintf('Incorrect items per page: %d. Only positive number allowed.', $page)
            );
        }

        return $this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
    }

    /**
     * Fetch first entity.
     *
     * @param array $parameters
     * @param int   $hydrationMode
     *
     * @return mixed|null
     * @throws \Doctrine\ORM\NonUniqueResultException
     * @throws \Xloit\Bridge\Doctrine\ORM\Exception\InvalidArgumentException
     */
    public function fetchOne(array $parameters = [], $hydrationMode = null)
    {
        $this->appendParameters($parameters);
        $this->limit(1, 0);

        return $this->getQuery()->getOneOrNullResult($hydrationMode);
    }

    /**
     * Fetch first column of first result row.
     *
     * @param array $parameters
     * @param int   $hydrationMode
     *
     * @return mixed
     * @throws \Doctrine\ORM\NonUniqueResultException
     * @throws \Doctrine\ORM\NoResultException
     * @throws \Xloit\Bridge\Doctrine\ORM\Exception\InvalidArgumentException
     */
    public function fetchSingle(array $parameters = [], $hydrationMode = null)
    {
        $this->appendParameters($parameters);
        $this->limit(1, 0);

        return $this->getQuery()->getSingleResult($hydrationMode);
    }

    /**
     * Fetch first column of first result row.
     *
     * @param array $parameters
     *
     * @return mixed
     * @throws \Doctrine\ORM\NonUniqueResultException
     * @throws \Doctrine\ORM\NoResultException
     * @throws \Xloit\Bridge\Doctrine\ORM\Exception\InvalidArgumentException
     */
    public function fetchSingleScalar(array $parameters = [])
    {
        return $this->fetchSingle($parameters, DoctrineQuery::HYDRATE_SINGLE_SCALAR);
    }

    /**
     * Fetch all entities.
     *
     * @param array $parameters
     * @param int   $hydrationMode
     *
     * @return array
     */
    public function fetchAll(array $parameters = [], $hydrationMode = DoctrineQuery::HYDRATE_OBJECT)
    {
        $this->appendParameters($parameters);

        return $this->getQuery()->getResult($hydrationMode);
    }

    /**
     * Fetch all entities.
     *
     * @param array $parameters
     *
     * @return array
     */
    public function fetchAllArray(array $parameters = [])
    {
        return $this->fetchAll($parameters, DoctrineQuery::HYDRATE_ARRAY);
    }

    /**
     * Fetch all entities.
     *
     * @param array $parameters
     *
     * @return array
     */
    public function fetchAllScalar(array $parameters = [])
    {
        return $this->fetchAll($parameters, DoctrineQuery::HYDRATE_SCALAR);
    }

    /**
     * Fetch all entities.
     *
     * @param array $parameters
     *
     * @return array
     */
    public function fetchAllSimple(array $parameters = [])
    {
        return $this->fetchAll($parameters, DoctrineQuery::HYDRATE_SIMPLEOBJECT);
    }

    /**
     *
     *
     * @param array $parameters
     *
     * @return $this This QueryBuilder instance.
     */
    public function appendParameters($parameters)
    {
        if (is_array($parameters)) {
            foreach ($parameters as $key => $value) {
                $type = null;

                if (is_array($value)) {
                    if (count($value) === 2) {
                        $type = $value[1];
                    }

                    $value = array_shift($value);
                } elseif ($value instanceof Parameter) {
                    $key   = $value->getKey();
                    $type  = $value->getType();
                    $value = $value->getName();
                }

                $this->setParameter($key, $value, $type);
            }
        }

        return $this;
    }

    /**
     * Auto append parameter and replace them with the parameter name.
     *
     * @param mixed  $value
     * @param string $type
     * @param string $columnName
     *
     * @return string The parameter name.
     */
    public function param($value, $type = null, $columnName = 'p')
    {
        $parameterName = $this->findUnusedParameterName($columnName);

        if ($type) {
            $this->appendParameters(
                [
                    $parameterName => [
                        $value,
                        $type
                    ]
                ]
            );
        } else {
            $this->appendParameters([$parameterName => $value]);
        }

        return sprintf(':%s', $parameterName);
    }

    /**
     *
     *
     * @param string $column
     *
     * @return string
     * @throws \ReflectionException
     */
    public function alias($column)
    {
        if (!is_string($column)
            || strpos('.', $column) !== false
            || !in_array($column, $this->getFieldNames(), true)
        ) {
            return $column;
        }

        return sprintf('%s.%s', $this->getEntityAlias(), $column);
    }

    /**
     * Adds support for magic finders.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     * @throws \Doctrine\ORM\ORMException
     * @throws \Xloit\Bridge\Doctrine\ORM\Exception\BadMethodCallException
     */
    public function __call($method, $arguments)
    {
        $commands = [
            'callFunctionalityFields' => [
                'prefix'  => true,
                'methods' => [
                    'where'          => true,
                    'filterBy'       => true,
                    'orderBy'        => false,
                    'orderAscBy'     => false,
                    'orderDescBy'    => false,
                    'addOrderBy'     => false,
                    'addOrderAscBy'  => false,
                    'addOrderDescBy' => false,
                    'groupBy'        => false,
                    'addGroupBy'     => false
                ]
            ],
            'callWhereClauseFields'   => [
                'prefix'  => true,
                'methods' => [
                    'GreaterThanEqual' => true,
                    'GreaterThan'      => true,
                    'LessThanEqual'    => true,
                    'LessThan'         => true,
                    'NotEqual'         => true,
                    'Equal'            => true,
                    'NotIn'            => true,
                    'In'               => true,
                    'NotLike'          => true,
                    'Like'             => true,
                    'IsNull'           => false,
                    'IsNotNull'        => false,
                    'IsEmpty'          => false,
                    'IsNotEmpty'       => false
                ]
            ]
        ];

        $condition    = null;
        $fieldName    = null;
        $methodName   = null;
        $functionName = null;

        foreach ($commands as $command => $commandOptions) {
            $parsed = $this->parseCallMethods(
                $method, $commandOptions['methods'], $arguments, $commandOptions['prefix']
            );

            $condition    = $parsed['condition'];
            $fieldName    = $parsed['fieldName'];
            $methodName   = $parsed['methodName'];
            $functionName = $command;

            if ($fieldName && $methodName) {
                break;
            }
        }

        if (!$fieldName || !$methodName) {
            throw new Exception\BadMethodCallException(sprintf('Undefined method "%s".', $method));
        }

        if ($functionName === 'callFunctionalityFields') {
            return $this->callFunctionalityFields($fieldName, $methodName, $arguments, $condition);
        }

        if ($functionName === 'callWhereClauseFields') {
            return $this->callWhereClauseFields($fieldName, $methodName, $arguments, $condition);
        }

        return $this->{$functionName}($fieldName, $methodName, $arguments, $condition);
    }

    /**
     * Get related entity class name
     *
     * @return string
     */
    protected function getEntityClassName()
    {
        return $this->getRepository()->getClassName();
    }

    /**
     *
     *
     * @param string $fieldName
     * @param mixed  $value
     * @param string $format
     * @param bool   $doNotCastDatetime
     *
     * @return string
     * @throws \Doctrine\ORM\Mapping\MappingException
     */
    protected function typeCastField($fieldName, $value, $format = null, $doNotCastDatetime = false)
    {
        $className     = $this->getEntityClassName();
        $classMetadata = $this->getEntityManager()->getClassMetadata($className);

        if (!$classMetadata->hasField($fieldName)) {
            return $value;
        }

        $field = $classMetadata->getFieldMapping($fieldName);

        switch ($field['type']) {
            case 'string':
                $value = (string)$value;
                break;
            case 'integer':
            case 'smallint':
                #case 'bigint':  // Don't try to manipulate bigints?
                $value = (int)$value;
                break;
            case 'boolean':
                $value = (bool)$value;
                break;
            case 'decimal':
                $value = (float)$value;
                break;
            case 'date':
                if ($value && !$doNotCastDatetime) {
                    if (!$format) {
                        $format = 'Y-m-d';
                    }

                    $value = DateTime::createFromFormat($format, $value);
                }
                break;
            case 'time':
                if ($value && !$doNotCastDatetime) {
                    if (!$format) {
                        $format = 'H:i:s';
                    }

                    $value = DateTime::createFromFormat($format, $value);
                }
                break;
            case 'datetime':
                if ($value && !$doNotCastDatetime) {
                    if (!$format) {
                        $format = 'Y-m-d H:i:s';
                    }

                    $value = DateTime::createFromFormat($format, $value);
                }
                break;
            case 'float':
                $value = (float)$value;
                break;
            default:
                break;
        }

        return $value;
    }

    /**
     * Find unused parameter name.
     *
     * @param string $columnName
     *
     * @return string
     */
    protected function findUnusedParameterName($columnName = 'p')
    {
        $parameters = $this->getParameters()->map(
            function($parameter) {
                /** @var Parameter $parameter */
                return $parameter->getName();
            }
        );
        $index      = 0;

        do {
            $parameterName = $columnName . $index;
            $index++;
        } while ($parameters->contains($parameterName));

        return $parameterName;
    }

    /**
     * A parseCallMethods function.
     *
     * @param string $method
     * @param array  $lists
     * @param array  $arguments
     * @param bool   $prefix
     *
     * @return array
     * @throws \Doctrine\ORM\ORMException
     */
    protected function parseCallMethods($method, array $lists, array $arguments, $prefix = true)
    {
        $condition  = null;
        $fieldName  = null;
        $methodName = null;

        foreach ($lists as $name => $requireArgs) {
            if (strpos($method, 'and') === 0) {
                $method    = lcfirst(substr($method, 3));
                $condition = 'and';
            } elseif (strpos($method, 'or') === 0) {
                $method    = lcfirst(substr($method, 2));
                $condition = 'or';
            }

            if (strlen($method) < strlen($name)) {
                continue;
            }

            $position = false;

            if ($prefix && strpos($method, $name) === 0) {
                $position = 0;
            } elseif (!$prefix) {
                $position = strpos($method, $name, strlen($name));
            }

            if ($position !== false) {
                /** @noinspection IsEmptyFunctionUsageInspection */
                if ($requireArgs && empty($arguments)) {
                    throw ORMException::findByRequiresParameter(sprintf('%s::%s', static::class, $method));
                }

                if ($prefix) {
                    $fieldName  = substr($method, strlen($name));
                    $methodName = $name;
                } elseif (!$prefix) {
                    $fieldName  = substr($method, 0, $position);
                    $methodName = $name;
                }
                break;
            }
        }

        if ($fieldName && $methodName) {
            $fieldName     = lcfirst(Inflector::classify($fieldName));
            $className     = $this->getEntityClassName();
            $classMetadata = $this->getEntityManager()->getClassMetadata($className);

            if (!$classMetadata->hasField($fieldName) && !$classMetadata->hasAssociation($fieldName)) {
                throw ORMException::invalidFindByCall($className, $fieldName, $method);
            }
        }

        return [
            'condition'  => $condition,
            'fieldName'  => $fieldName,
            'methodName' => $methodName
        ];
    }

    /**
     * A callFunctionalityFields function.
     *
     * @param string $fieldName
     * @param string $methodName
     * @param array  $arguments
     * @param string $condition
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    private function callFunctionalityFields($fieldName, $methodName, array $arguments = [], $condition = null)
    {
        switch ($methodName) {
            case 'where':
            case 'filterBy':
                if ($condition === 'or') {
                    $this->orWhere(sprintf('%s = %s', $this->alias($fieldName), $arguments[0]));
                } else {
                    $this->andWhere(sprintf('%s = %s', $this->alias($fieldName), $arguments[0]));
                }
                break;
            case 'orderBy':
            case 'orderAscBy':
            case 'orderDescBy':
                if ($methodName === 'orderAscBy') {
                    $arguments = ['ASC'];
                } elseif ($methodName === 'orderDescBy') {
                    $arguments = ['DESC'];
                }

                if (count($arguments) === 1) {
                    $this->orderBy($fieldName, $arguments[0]);
                } else {
                    $this->orderBy($fieldName);
                }
                break;
            case 'addOrderBy':
            case 'addOrderAscBy':
            case 'addOrderDescBy':
                if ($methodName === 'addOrderAscBy') {
                    $arguments = ['ASC'];
                } elseif ($methodName === 'addOrderDescBy') {
                    $arguments = ['DESC'];
                }

                if (count($arguments) === 1) {
                    $this->addOrderBy($fieldName, $arguments[0]);
                } else {
                    $this->addOrderBy($fieldName);
                }
                break;
            case 'groupBy':
                $this->groupBy($this->alias($fieldName));
                break;
            case 'addGroupBy':
                $this->addGroupBy($this->alias($fieldName));
                break;
            default:
                // Do Nothing
                break;
        }

        return $this;
    }

    /**
     * A callWhereClauseFields function.
     *
     * @param string $fieldName
     * @param string $methodName
     * @param array  $arguments
     * @param string $condition
     *
     * @return $this This QueryBuilder instance.
     * @throws \ReflectionException
     */
    private function callWhereClauseFields($fieldName, $methodName, array $arguments = [], $condition = null)
    {
        $expr = null;

        switch ($methodName) {
            case 'GreaterThanEqual':
                $expr = $this->expr()->gte($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'GreaterThan':
                $expr = $this->expr()->gt($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'LessThanEqual':
                $expr = $this->expr()->lte($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'LessThan':
                $expr = $this->expr()->lt($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'NotEqual':
                $expr = $this->expr()->neq($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'Equal':
                $expr = $this->expr()->eq($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'NotIn':
                $expr = $this->expr()->notIn($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'In':
                $expr = $this->expr()->in($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'NotLike':
                $expr = $this->expr()->notLike($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'Like':
                $expr = $this->expr()->like($this->alias($fieldName), $this->param($arguments[0]));
                break;
            case 'IsNull':
                $expr = $this->expr()->isNull($this->alias($fieldName));
                break;
            case 'IsNotNull':
                $expr = $this->expr()->isNotNull($this->alias($fieldName));
                break;
            case 'IsEmpty':
                $expr = $this->expr()->orX(
                    $this->expr()->eq($this->alias($fieldName), $this->param('')),
                    $this->expr()->isNull($this->alias($fieldName))
                );
                break;
            case 'IsNotEmpty':
                $expr = $this->expr()->andX(
                    $this->expr()->neq($this->alias($fieldName), $this->param('')),
                    $this->expr()->isNotNull($this->alias($fieldName))
                );
                break;
            default:
                // Do Nothing
                break;
        }

        if ($expr) {
            if ($condition === 'or') {
                $this->orWhere($expr);
            } else {
                $this->andWhere($expr);
            }
        }

        return $this;
    }
}


<?php
/**  ______ _                 _               _ ___
 *  |  ____| |               | |             | |__ \
 *  | |__  | |_   ___      __| |__   ___  ___| |  ) |
 *  |  __| | | | | \ \ /\ / /| '_ \ / _ \/ _ \ | / /
 *  | |    | | |_| |\ V  V / | | | |  __/  __/ |/ /_
 *  |_|    |_|\__, | \_/\_/  |_| |_|\___|\___|_|____|
 *             __/ |
 *            |___/
 *
 * Flywheel2: the inertia php framework
 *
 * @category	Flywheel2
 * @package		international
 * @author		wakaba <wakabadou@gmail.com>
 * @copyright	2011- Wakabadou honpo (http://www.wakabadou.net/) / Project ICKX (http://www.ickx.jp/)
 * @license		http://opensource.org/licenses/MIT The MIT License MIT
 * @version		2.0.0
 */

namespace ickx\fw2\international\encoding;

/**
 * 文字エンコーディングに係る処理を扱います。
 *
 * @see http://php.net/manual/ja/mbstring.encodings.php
 * @see http://php.net/manual/ja/mbstring.supported-encodings.php
 */
class Encoding {
	const UCS_4						= 'UCS-4';
	const UCS_4BE					= 'UCS-4BE';
	const UCS_4LE					= 'UCS-4LE';
	const UCS_2						= 'UCS-2';
	const UCS_2BE					= 'UCS-2BE';
	const UCS_2LE					= 'UCS-2LE';
	const UTF_32					= 'UTF-32';
	const UTF_32BE					= 'UTF-32BE';
	const UTF_32LE					= 'UTF-32LE';
	const UTF_16					= 'UTF-16';
	const UTF_16BE					= 'UTF-16BE';
	const UTF_16LE					= 'UTF-16LE';
	const UTF_7						= 'UTF-7';
	const UTF_7_IMAP				= 'UTF7-IMAP';
	const UTF_8						= 'UTF-8';
	const ASCII						= 'ASCII';
	const EUC_JP					= 'EUC-JP';
	const SJIS						= 'SJIS';
	const EUCJP_WIN					= 'eucJP-win';
	const SJIS_WIN					= 'SJIS-win';
	const ISO_2022_JP				= 'ISO-2022-JP';
	const ISO_2022_JP_MS			= 'ISO-2022-JP-MS';
	const CP932						= 'CP932';
	const CP51932					= 'CP51932';
	const SJIS_MAC					= 'SJIS-mac';
	const SJIS_MOBILE_DOCOMO		= 'SJIS-Mobile#DOCOMO';
	const SJIS_MOBILE_KDDI			= 'SJIS-Mobile#KDDI';
	const SJIS_MOBILE_SOFTBANK		= 'SJIS-Mobile#SOFTBANK';
	const UTF_8_MOBILE_DOCOMO		= 'UTF-8-Mobile#DOCOMO';
	const UTF_8_MOBILE_KDDI_A		= 'UTF-8-Mobile#KDDI-A';
	const UTF_8_MOBILE_KDDI_B		= 'UTF-8-Mobile#KDDI-B';
	const UTF_8_MOBILE_SOFTBANK		= 'UTF-8-Mobile#SOFTBANK';
	const ISO_2022_JP_MOBILE_KDDI	= 'ISO-2022-JP-MOBILE#KDDI';
	const JIS						= 'JIS';
	const JIS_MS					= 'JIS-ms';
	const CP50220					= 'CP50220';
	const CP50220RAW				= 'CP50220raw';
	const CP50221					= 'CP50221';
	const CP50222					= 'CP50222';
	const ISO_8859_1				= 'ISO-8859-1';
	const ISO_8859_2				= 'ISO-8859-2';
	const ISO_8859_3				= 'ISO-8859-3';
	const ISO_8859_4				= 'ISO-8859-4';
	const ISO_8859_5				= 'ISO-8859-5';
	const ISO_8859_6				= 'ISO-8859-6';
	const ISO_8859_7				= 'ISO-8859-7';
	const ISO_8859_8				= 'ISO-8859-8';
	const ISO_8859_9				= 'ISO-8859-9';
	const ISO_8859_10				= 'ISO-8859-10';
	const ISO_8859_13				= 'ISO-8859-13';
	const ISO_8859_14				= 'ISO-8859-14';
	const ISO_8859_15				= 'ISO-8859-15';
	const BYTE2BE					= 'byte2be';
	const BYTE2LE					= 'byte2le';
	const BYTE4BE					= 'byte4be';
	const BYTE4LE					= 'byte4le';
	const BASE64					= 'BASE64';
	const HTML_ENTITIES				= 'HTML-ENTITIES';
	const IANA_7BIT					= '7bit';
	const IANA_8BIT					= '8bit';
	const EUC_CN					= 'EUC-CN';
	const CP936						= 'CP936';
	const GB18030					= 'GB18030';
	const HZ						= 'HZ';
	const EUC_TW					= 'EUC-TW';
	const CP950						= 'CP950';
	const BIG_5						= 'BIG-5';
	const EUC_KR					= 'EUC-KR';
	const UHC						= 'UHC';
	const ISO_2022_KR				= 'ISO-2022-KR';
	const WINDOWS_1251				= 'Windows-1251';
	const WINDOWS_1252				= 'Windows-1252';
	const CP866						= 'CP866';
	const KOI8_R					= 'KOI8-R';
	const DEFAULT_ENCODING			= self::UTF_8;

	const ENCOFING_LIST				= [self::ASCII, self::JIS, self::JIS_MS, self::UTF_8, self::EUCJP_WIN, self::EUC_JP, self::SJIS_WIN, self::SJIS];

	/**
	 * 文字列のエンコーディングが指定されたものかどうかを判定します。
	 *
	 * @param	string	$string				エンコーディングを調べたい文字列
	 * @param	string	$target_encoding	マッチさせるエンコーディング
	 * @return	mixed	エンコーディングがマッチした場合はTRUE、そうでない場合はFALSE
	 * 					文字列のエンコーディング検出に失敗している場合はNULL
	 */
	public static function Compare ($string, $target_encoding, $encoding_list = self::ENCOFING_LIST) {
		$encoding = static::Detect($string, $encoding_list);
		return ($encoding === FALSE) ? FALSE : ($encoding === $target_encoding);
	}

	/**
	 * 文字列のエンコーディングを検出します。
	 *
	 * @param	string	$string			エンコーディングを検出したい文字列。
	 * @param	string	$encoding_list	エンコーディング検出順リスト カンマ区切りの文字列で記述 省略可能
	 * @return	mixed	検出したエンコーディング名 検出に失敗した場合はFALSE
	 */
	public static function Detect ($string, $encoding_list = self::ENCOFING_LIST) {
		return mb_detect_encoding($string, $encoding_list, TRUE);
	}

	/**
	 * 文字列のエンコーディングを合わせます。
	 *
	 * @param	string	$string			エンコードを合わせる文字列
	 * @param	string	$to_encoding	変換後のエンコード nullが指定された場合は内部エンコーディングを使用
	 * @param	string	$encoding_list	エンコード検出順リスト カンマ区切りの文字列で記述 省略可能
	 * @return	string	$to_encodingにエンコードを合わせた文字列
	 */
	public static function Adjust ($string, $to_encoding = null, $encoding_list = self::ENCOFING_LIST) {
		$to_encoding !== null ?: $to_encoding = mb_internal_encoding();
		if (static::Compare($string, $to_encoding, $encoding_list)) {
			return $string;
		}
		return static::Convert($string, $to_encoding, static::Detect($string, $encoding_list));
	}

	/**
	 *
	 */
	public static function GetOsViewEncoding ($os_name = \PHP_OS) {
		switch ($os_name) {
			case 'WINNT':
			case 'WIN32':
				return static::SJIS_WIN;
			default:
				return static::UTF_8;
		}
	}

	/**
	 * 文字列の文字エンコーディングを変換します。
	 *
	 * @param	string	$string			文字エンコーディング変換を行う文字列
	 * @param	string	$from_encoding	変換先の文字エンコーディング名
	 * @param	string	$from_encoding	変換元の文字エンコーディング名
	 * @return	string	文字エンコーディングを変換された文字列
	 */
	public static function Convert ($string, $to_encoding, $from_encoding = self::DEFAULT_ENCODING) {
		return mb_convert_encoding($string, $to_encoding, $from_encoding);
	}

	/**
	 * 文字列をシステムがデフォルトで使用している文字エンコーディングに変換します。
	 *
	 * @param	string	$string			文字エンコーディング変換を行う文字列
	 * @param	string	$from_encoding	変換先の文字エンコーディング名
	 * @return	string	文字エンコーディングを変換された文字列
	 */
	public static function ConvertToDefaultEncoding ($string, $from_encoding = null) {
		return mb_convert_encoding($string, static::DEFAULT_ENCODING, $from_encoding ?: static::Detect($string));
	}

	/**
	 * 配列全ての要素に文字エンコーディング変換を適用します。
	 *
	 * @param	array	$input			文字エンコーディング変換対象の配列
	 * @param	string	$to_encoding	変換先の文字エンコーディング名
	 * @param	string	$from_encoding	変換元の文字エンコーディング名
	 * @return	mixed	文字エンコーディングを変換された配列 変換に失敗している場合はboolean FALSE
	 */
	public static function ConvertForArray (array $input, $to_encoding, $from_encoding = self::DEFAULT_ENCODING) {
		$parameter_list = array(
			'to'	=> $to_encoding,
			'from'	=> $from_encoding,
		);
		return (array_walk($input, array('static', '_ConvertRapperForArrayWalk'), $parameter_list) === FALSE) ? FALSE : $input;
	}

	/**
	 * 配列全ての要素に文字エンコーディング変換を再帰的に適用します。
	 *
	 * @param	array	$input			文字エンコーディング変換対象の配列
	 * @param	string	$to_encoding	変換先の文字エンコーディング名
	 * @param	string	$from_encoding	変換元の文字エンコーディング名
	 * @return	mixed	再帰的に文字エンコーディングを変換された配列 変換に失敗している場合はboolean FALSE
	 */
	public static function ConvertArrayRecursive (array $input, $to_encoding, $from_encoding = self::DEFAULT_ENCODING) {
		$parameter_list = array(
			'to'	=> $to_encoding,
			'from'	=> $from_encoding,
		);
		return (array_walk_recursive($input, array('static', '_ConvertRapperForArrayWalk'), $parameter_list) === FALSE) ? FALSE : $input;
	}

	/**
	 * array_walk*関数用の文字エンコーディング変換ラッパーメソッドです。
	 *
	 * @param	mixed	&$value			文字エンコーディング変換を行う配列の値
	 * @param	mixed	$key			配列のキー
	 * @param	array	$parameter_list	変換用の設定値。次の構造となる
	 * array(
	 * 		'to'	=> 変換先エンコーディング名。必須
	 * 		'from'	=> 変換元エンコーディング名。必須
	 * )
	 */
	private static function _ConvertRapperForArrayWalk (&$value, $key, array $parameter_list) {
		if (!is_array($value)) {
			$value = static::convert($value, $parameter_list['to'], $parameter_list['from']);
		}
	}
}


<?php
namespace Core\Toolbox\Arrays;

/**
 * IsAssoc.php
 *
 * @author Michael "Tekkla" Zorn <tekkla@tekkla.de>
 * @copyright 2016
 * @license MIT
 */
class IsAssoc extends AbstractArray
{

    /**
     * Checks a value whether to be an array, if its empty and when not an empty array if it's an associative one.
     *
     * @return bool
     */
    public function isAssoc(): bool
    {
        if (empty($this->array)) {
            return false;
        }

        return (bool) count(array_filter(array_keys($this->array), 'is_string'));
    }
}


<?php

declare(strict_types=1);

namespace Psi\Component\Description;

use Psi\Component\Description\Schema\Schema;

/**
 * This class is the main point of entry for this component.
 *
 * It provides descriptions for objects. Descriptions are populated by
 * "enhancers" as given in the constructor. The order of the enhancers is important
 */
class DescriptionFactory
{
    /**
     * @var DescriptionEnhancerInterface[]
     */
    private $enhancers = [];

    /**
     * @var SubjectResolverInterface[]
     */
    private $resolvers = [];

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

    public function __construct(array $enhancers, Schema $schema = null, array $resolvers = [])
    {
        // type safety ...
        array_walk($enhancers, function (EnhancerInterface $enhancer) {
        });
        array_walk($resolvers, function (SubjectResolverInterface $enhancer) {
        });

        $this->enhancers = $enhancers;
        $this->resolvers = $resolvers;
        $this->schema = $schema;
    }

    /**
     * Return a description of the given subject.
     */
    public function describe(Subject $subject): DescriptionInterface
    {
        $description = $this->createDescription();

        foreach ($this->resolvers as $resolver) {
            $subject = $resolver->resolve($subject);
        }

        foreach ($this->enhancers as $enhancer) {
            if (false === $enhancer->supports($subject)) {
                continue;
            }

            $enhancer->enhanceFromClass($description, $subject->getClass());

            if ($subject->hasObject()) {
                $enhancer->enhanceFromObject($description, $subject);
            }
        }

        return $description;
    }

    private function createDescription(): DescriptionInterface
    {
        $description = new Description();

        if ($this->schema) {
            $description = new ValidatedDescription($description, $this->schema);
        }

        return $description;
    }
}


<?php

namespace ultimadark\Logger;

/**
 * Class to log what happens.
 *
 */
class CLog
{

    /**
     * Properties
     *
     */
    private $timestamp = array();
    private $pos = 0;
    private $precision;

    /**
     * Constructor
     *
     * @param int $precision the precision of the rounding when presenting results.
     */
    public function __construct($precision = 4)
    {
        $this->precision = $precision;
    }

    /**
     * Timestamp, log a event with a time.
     *
     * @param string $domain is the module or class.
     * @param string $where a more specific place in the domain.
     * @param string $comment on the timestamp.
     *
     */
    public function timestamp($domain, $where, $comment = null)
    {
        $now = microtime(true);
        $this->timestamp[] = array(
          'domain'  => $domain,
          'where'   => $where,
          'comment' => $comment,
          'when'    => $now,
          'memory'  => memory_get_usage(true),
          'duration'=> 0,
        );

        if ($this->pos > 0) {
            $this->timestamp[$this->pos - 1]['memory-peak'] = memory_get_peak_usage(true);
            $this->timestamp[$this->pos - 1]['duration']    = $now - $this->timestamp[$this->pos - 1]['when'];
        }

        $this->pos++;
    }

    /**
     * Print all timestamp to a table.
     *
     * @return string with a html-table to display all timestamps.
     *
     */
    public function timestampAsTable()
    {

        // Set up the table
        $first = $this->timestamp[0]['when'];
        $last = $this->timestamp[count($this->timestamp) - 1]['when'];
        $html = "

Timestamps

Domain	Where	When (sec)	Duration (sec)	Percent	Memory (MB)	Memory peak (MB)	Comment
";
        $right = ' style="text-align: right;"';
        $total = array('domain' => array(), 'where' => array());

        $totalDuration = $last - $first;

        // Create the main table
        foreach ($this->timestamp as $val) {
            $when     = $val['when'] - $first;
            $duration = round($val['duration'], $this->precision);
            $percent  = round(($when) / $totalDuration * 100);
            $memory   = round($val['memory'] / 1024 / 1024, 2);
            $peak     = isset($val['memory-peak']) ? round($val['memory-peak'] / 1024 / 1024, 2): 0;
            $when     = round($when, $this->precision);
            $html .= "
{$val['domain']}	{$val['where']}	{$when}	{$duration}	{$percent}	{$memory}	{$peak}	{$val['comment']}
";
            @$total['domain'][$val['domain']] += $val['duration'];
            @$total['where'][$val['where']] += $val['duration'];
        }

        $html .= "

Duration per domain

Domain	Duration	Percent
";

        // Create the table grouped by domain
        arsort($total['domain']);

        foreach ($total['domain'] as $key => $val) {
            if ($totalDuration != 0) {
                $percent = round($val / $totalDuration * 100, 1);
            } else {
                $percent = 100;
            }

            $roundedDuration = round($val, $this->precision);
            $html .= "
{$key}	{$roundedDuration}	{$percent}
";
        }

        $html .= "

Duration per area

Area	Duration	Percent
";

        // Create the table grouped by area
        arsort($total['where']);

        foreach ($total['where'] as $key => $val) {
            if ($totalDuration != 0) {
                $percent = round($val / $totalDuration * 100, 1);
            } else {
                $percent = 100;
            }

            $roundedDuration = round($val, $this->precision);
            $html .= "
{$key}	{$roundedDuration}	{$percent}
";
        }

        $html .= "
";

        return $html;
    }

    /**
     * Returns load time of the page
     *
     * @return The page load time.
     *
     */
    public function pageLoadTime()
    {
        $first = $this->timestamp[0]['when'];
        $last = $this->timestamp[count($this->timestamp) - 1]['when'];
        $loadtime = round($last - $first, 3);
        return $loadtime;
    }

    /**
     * Get the memory peak.
     *
     * @return The memory peak.
     *
     */
    public function memoryPeak()
    {
        $peak = round(memory_get_peak_usage(true) / 1024 / 1024, 2);
        return $peak;
    }

    /**
     * Get the number of timestamps made so far.
     *
     * @return Number of timestamps.
     *
     */
    public function numberOfTimestamps()
    {
        return count($this->timestamp);
    }

    /**
     * Get the most time consuming domain.
     *
     * @return The most time consuming domain.
     *
     */
    public function mostTimeConsumingDomain()
    {
        $total = array();

        // Gather total duration data of each domain
        foreach ($this->timestamp as $val) {
            $duration = round($val['duration'], $this->precision);
            @$total[$val['domain']] += $duration;
        }

        // Get the key of the most time consuming element
        $mostConsumingTime = 0;
        $mostConsumingDomain = "";
        foreach ($total as $key => $val) {
            if ($val >= $mostConsumingTime) {
                $mostConsumingDomain = $key;
                $mostConsumingTime = $val;
            }
        }

        return $mostConsumingDomain;
    }
}


<?php
/**
 * @file
 * Using Vincenty's indirect formula to calculate distance.
 */

namespace Lootils\Geo\Method;

use \Lootils\Geo\Earth;
use \Lootils\Geo\LocationInterface;
use \Lootils\Geo\Method\DistanceInterface;

class Vincenty extends Earth implements DistanceInterface {

  /**
   * Calculate the distance between two locations.
   *
   * This uses Vincenty's formulae. For more information on this method
   * see https://en.wikipedia.org/wiki/Vincenty's_formulae. There is also lots
   * of good info at http://www.linz.govt.nz/geodetic/.
   *
   * Note, Vincenty's formulae is not known for its performance. It's more
   * accurate than other methods but takes more processing time to calculate.
   */
  public function distance(LocationInterface $location1, LocationInterface $location2) {
    $lat1 = deg2rad($location1->latitude());
    $lat2 = deg2rad($location2->latitude());
    $lon1 = deg2rad($location1->longitude());
    $lon2 = deg2rad($location2->longitude());

    // WGS-84 ellipsoid
    $a = $this->earthRadiusSemimajor();
    $b = $this->earthRadiusSemiminor();
    $f = $this->earthFlattening();

    $L = $lon2 - $lon1;

    $U1 = atan((1-$f) * tan($lat1));
    $U2 = atan((1-$f) * tan($lat2));

    $sinU1 = sin($U1);
    $cosU1 = cos($U1);
    $sinU2 = sin($U2);
    $cosU2 = cos($U2);

    $lambda = $L;
    $lambdaP = 2 * pi();

    $iterLimit = 20;

    while (abs($lambda - $lambdaP) > 1e-12 && --$iterLimit > 0) {
      $sinLambda = sin($lambda);
      $cosLambda = cos($lambda);
      $sinSigma = sqrt(($cosU2 * $sinLambda) * ($cosU2 * $sinLambda) + ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) * ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda));

      if ($sinSigma == 0) {
        return 0;
      }

      $cosSigma = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
      $sigma = atan2($sinSigma, $cosSigma); // was atan2
      $alpha = asin($cosU1 * $cosU2 * $sinLambda / $sinSigma);
      $cosSqAlpha = cos($alpha) * cos($alpha);
      $cos2SigmaM = $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
      $C = $f / 16 * $cosSqAlpha * (4 + $f * (4 - 3 * $cosSqAlpha));
      $lambdaP = $lambda;
      $lambda = $L + (1 - $C) * $f * sin($alpha) * ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
    }
    if ($iterLimit == 0) {
      return FALSE; // Oh no... we have a failure.
    }

    $uSq = $cosSqAlpha * ($a * $a - $b * $b) / ($b * $b);
    $A = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
    $B = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));

    $deltaSigma = $B * $sinSigma * ($cos2SigmaM + $B / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM) - $B / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));

    $s = $b * $A * ($sigma - $deltaSigma);

    return round($s, 3); // round to 1mm precision;
  }

}


<?php

namespace Reaction\Widgets\Bootstrap;

use Reaction\Helpers\Html;
use Reaction\Helpers\ArrayHelper;

/**
 * Class Card
 * @package Reaction\Widgets\Bootstrap
 */
class Card extends Widget
{
    public $tag = 'div';

    public $headerImage;

    public $headerImageOptions = [];

    public $header;

    public $headerOptions = [];

    public $body;

    public $bodyOptions = [];

    public $footer;

    public $footerOptions = [];

    public $footerImage;

    public $footerImageOptions = [];

    public $overlayImage;

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        Html::addCssClass($this->options, ['widget' => 'card']);
        ob_start();
        ob_implicit_flush(true);
    }

    /**
     * @inheritdoc
     */
    public function run()
    {
        $content = [];
        $content[] = $this->htmlHlp->beginTag($this->tag, $this->options);
        $content[] = $this->renderHeader();
        $content[] = $this->renderHeaderImage();
        $content[] = $this->renderOverlayImage();
        $content[] = $this->renderBody(null, true);
        $content[] = $this->renderFooter();
        $content[] = $this->htmlHlp->endTag($this->tag);
        return implode("\n", $content);
    }

    /**
     * Render card header
     * @return string
     */
    protected function renderHeader() {
        if (!isset($this->header)) {
            return '';
        }
        if (isset($this->headerOptions)) {
            Html::addCssClass($this->headerOptions, ['widget' => 'card-header']);
            $tag = ArrayHelper::remove($this->headerOptions, 'tag', 'div');
            $header = Html::tag($tag, $this->header, $this->headerOptions);
        } else {
            $header = $this->header;
        }
        return $header;
    }

    /**
     * Render card top image
     * @return string
     */
    protected function renderHeaderImage() {
        if (!isset($this->headerImage)) {
            return '';
        }
        Html::addCssClass($this->headerImageOptions, ['widget' => 'card-img-top']);
        return $this->htmlHlp->img($this->headerImage, $this->headerImageOptions);
    }

    /**
     * Render card bottom image
     * @return string
     */
    protected function renderFooterImage() {
        if (!isset($this->footerImage)) {
            return '';
        }
        Html::addCssClass($this->footerImageOptions, ['widget' => 'card-img-bottom']);
        return $this->htmlHlp->img($this->footerImage, $this->footerImageOptions);
    }

    /**
     * Render card bottom image
     * @return string
     */
    protected function renderOverlayImage() {
        if (!isset($this->overlayImage)) {
            return '';
        }
        Html::addCssClass($this->footerImageOptions, ['widget' => 'card-img-bottom']);
        return $this->htmlHlp->img($this->overlayImage, ['class' => 'card-img']);
    }

    /**
     * Render card body
     * @param string $body
     * @param bool   $includeOb
     * @return string
     */
    protected function renderBody($body = null, $includeOb = false) {
        $body = isset($body) ? $body : $this->body;
        $bodyContent = '';
        $bodyTag = 'div';
        if (is_array($body)) {
            $bodyRows = [];
            foreach ($body as $bodyRow) {
                $bodyRowContent = $this->renderBody($bodyRow);
                if (empty($bodyRowContent)) {
                    continue;
                }
                $bodyRows[] = $bodyRowContent;
            }
            $bodyContent = implode("\n", $bodyRows);
        } else {
            if (isset($this->bodyOptions)) {
                if (isset($this->overlayImage)) {
                    Html::addCssClass($this->bodyOptions, ['widget' => 'card-img-overlay']);
                } else {
                    Html::addCssClass($this->bodyOptions, ['widget' => 'card-body']);
                }
                $bodyTag = ArrayHelper::remove($this->bodyOptions, 'tag', 'div');
                $bodyOptions = $this->bodyOptions;
            }
            if (isset($body)) {
                $bodyContent .= $body;
            }
        }
        if ($includeOb) {
            $bodyContent .= ob_get_clean();
        }
        return isset($bodyOptions) ? $this->htmlHlp->tag($bodyTag, $bodyContent, $bodyOptions) : $bodyContent;
    }

    /**
     * Render card footer
     * @return string
     */
    protected function renderFooter() {
        if (!isset($this->footer)) {
            return '';
        }
        if (isset($this->footerOptions)) {
            Html::addCssClass($this->footerOptions, ['widget' => 'card-footer']);
            $tag = ArrayHelper::remove($this->footerOptions, 'tag', 'div');
            $footer = $this->htmlHlp->tag($tag, $this->footer, $this->footerOptions);
        } else {
            $footer = $this->footer;
        }
        return $footer;
    }
}


<?php

namespace tourze\Http\Exception;

use tourze\Base\Exception\BaseException;
use tourze\Base\Helper\Url;
use tourze\Base\Base;

/**
 * 跳转使用异常来实现跳转
 *
 * @package tourze\Http\Exception
 */
abstract class RedirectException extends ExpectedException
{

    /**
     * 跳转到指定URI
     *
     * @param  string $uri
     * @return $this
     */
    public function location($uri = null)
    {
        if (null === $uri)
        {
            return $this->headers('Location');
        }

        if (false === strpos($uri, '://'))
        {
            $uri = Url::site($uri, true, ! empty(Base::$indexFile));
        }

        $lastTime = gmdate("D, d M Y H:i:s", time()).' GMT+0800';
        $this->headers('Cache-Control', 'no-cache');
        $this->headers('Last Modified', $lastTime);
        $this->headers('Last Fetched', $lastTime);
        $this->headers('Expires', 'Thu Jan 01 1970 08:00:00 GMT+0800');
        $this->headers('Location', $uri);

        return $this;
    }

    /**
     * 校验是否有跳转地址了
     *
     * @throws BaseException
     * @return bool
     */
    public function check()
    {
        if (null === $this->headers('location'))
        {
            throw new BaseException("A 'location' must be specified for a redirect");
        }

        return true;
    }

}


<?php

namespace Reaction\Console\Routes;

use Reaction\Console\Web\RequestHelper;
use Reaction\Exceptions\Exception;
use Reaction\Exceptions\HttpException;
use Reaction\Helpers\ArrayHelper;
use Reaction\Helpers\Console;
use Reaction\Helpers\Inflector;
use Reaction\Helpers\ReflectionHelper;
use Reaction\Promise\ExtendedPromiseInterface;
use function Reaction\Promise\resolve;
use Reaction\RequestApplicationInterface;
use Reaction\Web\Response;
use Reaction\Web\ResponseBuilderInterface;

/**
 * Class Controller
 * @package Reaction\Console\Routes
 * @property string|null $currentAction
 */
class Controller extends \Reaction\Routes\Controller
{

    /**
     * @var bool whether to enable ANSI color in the output.
     * If not set, ANSI color will only be enabled for terminals that support it.
     */
    public $color;

    /**
     * @var bool whether to run the command interactively.
     */
    public $interactive = true;

    /**
     * @var bool whether to display help information about current command.
     */
    public $help;

    /**
     * @var RequestApplicationInterface
     */
    public $app;

    /**
     * @var array the options passed during execution.
     */
    protected $_passedOptions = [];

    /**
     * @inheritdoc
     */
    public function resolveError(RequestApplicationInterface $app, \Throwable $exception, $asPlainText = false)
    {
        return $this->resolveErrorAsPlainText($app, $exception);
    }

    /**
     * Get current action
     * @param RequestApplicationInterface|null $app
     * @return string|null
     */
    public function getCurrentAction(RequestApplicationInterface $app = null)
    {
        $app = $app !== null ? $app : $this->app;
        return $app !== null ? $app->getRoute()->getAction() : null;
    }

    /**
     * Resolve error as pain text
     * @param RequestApplicationInterface $app
     * @param \Throwable                  $exception
     * @return ResponseBuilderInterface
     */
    protected function resolveErrorAsPlainText(RequestApplicationInterface $app, \Throwable $exception)
    {
        $data = $this->getErrorData($exception);
        if ($exception instanceof \Reaction\Console\Exception) {
            $this->stdout($this->ansiFormat($data['message'], Console::FG_RED) . "\n");
            return null;
        } else {
            $body = $data['name'] . ' (' . $data['code'] . ')' . "\n";
            $body .= $data['message'] !== "" ? $data['message'] . "\n" : '';
            $body .= $data['file'] !== "" ? $data['file'] : '';
            $body .= $data['line'] !== "" ? " #" . $data['line'] . "\n" : '';
        }

        if (!$exception instanceof HttpException && !empty($data['trace'])) {
            $body .= $data['trace'] . "\n";
        }

        $app->response->setBody($body)
            ->setStatusCodeByException($exception)
            ->setFormat(Response::FORMAT_RAW);
        return $app->response;
    }

    /**
     * @inheritdoc
     */
    public function resolveAction(RequestApplicationInterface $app, string $action, ...$params)
    {
        /** @var RequestHelper $req */
        $req = $app->reqHelper;
        $paramsConsole = $req->getConsoleParams();
        $params = ArrayHelper::merge($params, $paramsConsole);
        array_unshift($params, $app);
        $this->processActionParams($action, $params);
        if ($this->help && $this->getUniqueId() !== 'help') {
            $routePath = $this->getUniqueId() . '/' . $action;
            return $app->resolveAction('help', 'GET', [$routePath]);
        }
        return parent::resolveAction($app, $action, ...$params);
    }

    /**
     * Get route group name, if empty no grouping
     * @return string
     */
    public function group()
    {
        $namespace = \Reaction::$app->router->getRelativeControllerNamespace(static::class);
        $namespace = substr($namespace, 0, -10);
        $namespaceArray = explode('\\', $namespace);
        array_walk($namespaceArray, function(&$value) {
            $value = Inflector::camel2id($value, '-');
        });
        return implode('/', $namespaceArray);
    }

    /**
     * Returns a value indicating whether ANSI color is enabled.
     *
     * ANSI color is enabled only if [[color]] is set true or is not set
     * and the terminal supports ANSI color.
     *
     * @param resource $stream the stream to check.
     * @return bool Whether to enable ANSI style in output.
     */
    public function isColorEnabled($stream = \STDOUT)
    {
        return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color;
    }

    /**
     * Formats a string with ANSI codes.
     *
     * You may pass additional parameters using the constants defined in [[\yii\helpers\Console]].
     *
     * Example:
     *
     * ```
     * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
     * ```
     *
     * @param string $string the string to be formatted
     * @return string
     */
    public function ansiFormat($string)
    {
        if ($this->isColorEnabled()) {
            $args = func_get_args();
            array_shift($args);
            $string = Console::ansiFormat($string, $args);
        }

        return $string;
    }

    /**
     * Prints a string to STDOUT.
     *
     * You may optionally format the string with ANSI codes by
     * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
     *
     * Example:
     *
     * ```
     * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
     * ```
     *
     * @param string $string the string to print
     * @return int|bool Number of bytes printed or false on error
     */
    public function stdout($string)
    {
        if ($this->isColorEnabled()) {
            $args = func_get_args();
            array_shift($args);
            $string = Console::ansiFormat($string, $args);
        }

        return Console::stdout($string);
    }

    /**
     * Prompts the user for input and validates it.
     *
     * @param string $text prompt string
     * @param array $options the options to validate the input:
     *
     *  - required: whether it is required or not
     *  - default: default value if no input is inserted by the user
     *  - pattern: regular expression pattern to validate user input
     *  - validator: a callable function to validate input. The function must accept two parameters:
     *      - $input: the user input to validate
     *      - $error: the error value passed by reference if validation failed.
     *
     * An example of how to use the prompt method with a validator function.
     *
     * ```php
     * $code = $this->prompt('Enter 4-Chars-Pin', ['required' => true, 'validator' => function($input, &$error) {
     *     if (strlen($input) !== 4) {
     *         $error = 'The Pin must be exactly 4 chars!';
     *         return false;
     *     }
     *     return true;
     * }]);
     * ```
     *
     * @return ExtendedPromiseInterface with string the user input
     */
    public function prompt($text, $options = [])
    {
        if ($this->interactive) {
            return Console::prompt($text, $options);
        }

        $default = isset($options['default']) ? $options['default'] : '';
        return resolve($default);
    }

    /**
     * Asks user to confirm by typing y or n.
     *
     * A typical usage looks like the following:
     *
     * ```php
     * if ($this->confirm("Are you sure?")) {
     *     echo "user typed yes\n";
     * } else {
     *     echo "user typed no\n";
     * }
     * ```
     *
     * @param string $message to echo out before waiting for user input
     * @param bool $default this value is returned if no selection is made.
     * @return ExtendedPromiseInterface with bool whether user confirmed.
     * Will return true if [[interactive]] is false.
     */
    public function confirm($message, $default = false)
    {
        if ($this->interactive) {
            return Console::confirm($message, $default);
        }

        return resolve(true);
    }

    /**
     * Gives the user an option to choose from. Giving '?' as an input will show
     * a list of options to choose from and their explanations.
     *
     * @param string $prompt the prompt message
     * @param array $options Key-value array of options to choose from
     *
     * @return ExtendedPromiseInterface with string An option character the user chose
     */
    public function select($prompt, $options = [])
    {
        return Console::select($prompt, $options);
    }

    /**
     * Returns the names of valid options for the action (id)
     * An option requires the existence of a public member variable whose
     * name is the option name.
     * Child classes may override this method to specify possible options.
     *
     * Note that the values setting via options are not available
     * until [[beforeAction()]] is being called.
     *
     * @param string $actionId the action id of the current request
     * @return string[] the names of the options valid for the action
     */
    public function options($actionId = null)
    {
        // $actionId might be used in subclasses to provide options specific to action id
        return ['color', 'interactive', 'help'];
    }

    /**
     * Returns option alias names.
     * Child classes may override this method to specify alias options.
     *
     * @return array the options alias names valid for the action
     * where the keys is alias name for option and value is option name.
     *
     * @see options()
     */
    public function optionAliases()
    {
        return [
            'h' => 'help',
        ];
    }

    /**
     * Returns properties corresponding to the options for the action id
     * Child classes may override this method to specify possible properties.
     *
     * @param string $actionID the action id of the current request
     * @return array properties corresponding to the options for the action
     */
    public function getOptionValues($actionID = null)
    {
        // $actionId might be used in subclasses to provide properties specific to action id
        $properties = [];
        foreach ($this->options($actionID) as $property) {
            $properties[$property] = $this->$property;
        }

        return $properties;
    }

    /**
     * Returns the names of valid options passed during execution.
     *
     * @return array the names of the options passed during execution
     */
    public function getPassedOptions()
    {
        return $this->_passedOptions;
    }

    /**
     * Returns the properties corresponding to the passed options.
     *
     * @return array the properties corresponding to the passed options
     */
    public function getPassedOptionValues()
    {
        $properties = [];
        foreach ($this->_passedOptions as $property) {
            $properties[$property] = $this->$property;
        }

        return $properties;
    }

    /**
     * Returns one-line short summary describing this controller.
     *
     * You may override this method to return customized summary.
     * The default implementation returns first line from the PHPDoc comment.
     *
     * @return string
     */
    public function getHelpSummary()
    {
        return ReflectionHelper::getClassDocSummary($this);
    }

    /**
     * Returns help information for this controller.
     *
     * You may override this method to return customized help.
     * The default implementation returns help information retrieved from the PHPDoc comment.
     * @return string
     */
    public function getHelp()
    {
        return ReflectionHelper::getClassDocFull($this);
    }

    /**
     * Returns a one-line short summary describing the specified action.
     * @param string $actionId action to get summary for
     * @return string a one-line short summary describing the specified action.
     */
    public function getActionHelpSummary($actionId)
    {
        $method = static::getActionMethod($actionId);
        return ReflectionHelper::getMethodDocSummary($method, $this);
    }

    /**
     * Returns the detailed help information for the specified action.
     * @param string $actionId action to get help for
     * @return string the detailed help information for the specified action.
     */
    public function getActionHelp($actionId)
    {
        $method = static::getActionMethod($actionId);
        return ReflectionHelper::getMethodDocFull($method, $this);
    }

    /**
     * Returns the help information for the anonymous arguments for the action.
     *
     * The returned value should be an array. The keys are the argument names, and the values are
     * the corresponding help information. Each value must be an array of the following structure:
     *
     * - required: boolean, whether this argument is required.
     * - type: string, the PHP type of this argument.
     * - default: string, the default value of this argument
     * - comment: string, the comment of this argument
     *
     * The default implementation will return the help information extracted from the doc-comment of
     * the parameters corresponding to the action method.
     *
     * @param string $actionId
     * @return array the help information of the action arguments
     */
    public function getActionArgsHelp($actionId)
    {
        $methodName = static::getActionMethod($actionId);
        $method = ReflectionHelper::getMethodReflection($this, $methodName);
        $tags = ReflectionHelper::getMethodDocTags($method, $this);
        $params = isset($tags['param']) ? (array)$tags['param'] : [];

        $args = [];

        /** @var \ReflectionParameter $reflection */
        foreach ($method->getParameters() as $i => $reflection) {
            if ($reflection->getClass() !== null) {
                continue;
            }
            $name = $reflection->getName();
            $tag = isset($params[$i]) ? $params[$i] : '';
            if (preg_match('/^(\S+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
                $type = $matches[1];
                $comment = $matches[3];
            } else {
                $type = null;
                $comment = $tag;
            }
            if ($reflection->isDefaultValueAvailable()) {
                $args[$name] = [
                    'required' => false,
                    'type' => $type,
                    'default' => $reflection->getDefaultValue(),
                    'comment' => $comment,
                ];
            } else {
                $args[$name] = [
                    'required' => true,
                    'type' => $type,
                    'default' => null,
                    'comment' => $comment,
                ];
            }
        }

        return $args;
    }

    /**
     * Returns the help information for the options for the action.
     *
     * The returned value should be an array. The keys are the option names, and the values are
     * the corresponding help information. Each value must be an array of the following structure:
     *
     * - type: string, the PHP type of this argument.
     * - default: string, the default value of this argument
     * - comment: string, the comment of this argument
     *
     * The default implementation will return the help information extracted from the doc-comment of
     * the properties corresponding to the action options.
     *
     * @param string $actionId
     * @return array the help information of the action options
     */
    public function getActionOptionsHelp($actionId)
    {
        $optionNames = $this->options($actionId);
        if (empty($optionNames)) {
            return [];
        }

        $class = ReflectionHelper::getClassReflection($this);
        $options = [];
        foreach ($class->getProperties() as $property) {
            $name = $property->getName();
            if (!in_array($name, $optionNames, true)) {
                continue;
            }
            $defaultValue = $property->getValue($this);
            $tags = ReflectionHelper::getPropertyDocTags($property);

            // Display camelCase options in kebab-case
            $name = Inflector::camel2id($name, '-', true);

            if (isset($tags['var']) || isset($tags['property'])) {
                $doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
                if (is_array($doc)) {
                    $doc = reset($doc);
                }
                if (preg_match('/^(\S+)(.*)/s', $doc, $matches)) {
                    $type = $matches[1];
                    $comment = $matches[2];
                } else {
                    $type = null;
                    $comment = $doc;
                }
                $options[$name] = [
                    'type' => $type,
                    'default' => $defaultValue,
                    'comment' => $comment,
                ];
            } else {
                $options[$name] = [
                    'type' => null,
                    'default' => $defaultValue,
                    'comment' => '',
                ];
            }
        }

        return $options;
    }

    /**
     * Process action params array
     * @param string $actionId
     * @param array  $params
     * @throws Exception
     */
    protected function processActionParams(&$actionId, &$params = [])
    {
        $actionMethod = $this->normalizeActionName($actionId);
        if (!empty($params)) {
            // populate options here so that they are available in beforeAction().
            $options = $this->options($actionId);
            if (isset($params['_aliases'])) {
                $optionAliases = $this->optionAliases();
                foreach ($params['_aliases'] as $name => $value) {
                    if (array_key_exists($name, $optionAliases)) {
                        $params[$optionAliases[$name]] = $value;
                    } else {
                        throw new Exception('Unknown alias: -{name}', ['name' => $name]);
                    }
                }
                unset($params['_aliases']);
            }
            foreach ($params as $name => $value) {
                // Allow camelCase options to be entered in kebab-case
                if (!in_array($name, $options, true) && strpos($name, '-') !== false) {
                    $kebabName = $name;
                    $altName = lcfirst(Inflector::id2camel($kebabName));
                    if (in_array($altName, $options, true)) {
                        $name = $altName;
                    }
                }

                if (in_array($name, $options, true)) {
                    $default = $this->$name;
                    if (is_array($default)) {
                        $this->$name = preg_split('/\s*,\s*(?![^()]*\))/', $value);
                    } elseif ($default !== null) {
                        settype($value, gettype($default));
                        $this->$name = $value;
                    } else {
                        $this->$name = $value;
                    }
                    $this->_passedOptions[] = $name;
                    unset($params[$name]);
                    if (isset($kebabName)) {
                        unset($params[$kebabName]);
                    }
                } elseif (!is_int($name)) {
                    throw new Exception('Unknown option: --{name}', ['name' => $name]);
                }
            }
        }
        $argsData = ReflectionHelper::checkMethodArguments($params, $actionMethod, $this);
        if (!empty($argsData)) {
            $errorMessage = $this->ansiFormat("Controller action arguments error\n", Console::FG_RED, Console::BOLD);
            foreach ($argsData as $argName => $error) {
                $argError = $error === ReflectionHelper::ARG_REQUIRED_MISSING
                    ? "Missing required argument"
                    : "Argument type mismatch";
                $errorMessage .= "\t - "
                    . $this->ansiFormat($argName, Console::FG_GREEN, Console::BOLD)
                    . " - " . $this->ansiFormat($argError, Console::FG_RED)
                    . "\n";
            }
            throw new \Reaction\Console\Exception($errorMessage);
        }
    }
}


<?php

/**
 * Jin Framework
 * Diatem
 */

namespace Jin2\Utils;

use Jin2\Utils\StringTools;

/**
 * Boite à outil pour les numériques
 */
class NumberTools
{

  /**
   * Permet de formater un nombre selon des critères précis
   *
   * @param  integer  $number          Nombre à formater
   * @param  integer  $decimals        Nombre de décimales à conserver
   * @param  string   $thousandMarker  (optional) Séparateur utiliser pour séparer les milliers (espace par défaut)
   * @param  string   $decimalMarker   (optional) Séparateur à utiliser pour les virgules (. par défaut)
   * @return string                    Nombre formaté
   */
  public static function numberFormat($number, $decimals, $thousandMarker = ' ', $decimalMarker = '.')
  {
    return number_format($number, $decimals, $decimalMarker, $thousandMarker);
  }

  /**
   * Arrondi un nombre selon les règles monétaires européennes
   *
   * @param  integer  $number  Nombre à arrondir
   * @return integer           Nombre arrondi
   */
  public static function euroRound($number)
  {
    return round((float) $number, 2, PHP_ROUND_HALF_EVEN);
  }

  /**
   * Arrondi à l'entier inférieur
   *
   * @param  integer  $number  Nombre à arrondir
   * @return integer           Nombre arrondi
   */
  public static function floor($number)
  {
    return floor($number);
  }

  /**
   * Arrondi selon les règles standard
   *
   * @param  integer  $number    Nombre à arrondir
   * @param  integer  $decimals  (optional) Nombre de décimales à conserver (2 par défaut)
   * @return integer             Nombre arrondi
   */
  public static function round($number, $decimals = 0)
  {
    return round((float) $number, $decimals);
  }

  /**
   * Arrondi à l'entier supérieur
   *
   * @param  integer  $number  Nombre à arrondir
   * @return integer           Nombre arrondi
   */
  public static function ceil($number)
  {
    return ceil($number);
  }

  /**
   * Permet de savoir si un nombre est un entier
   *
   * @param  integer  $number  Nombre à tester
   * @return boolean           TRUE si le nombre est un entier
   */
  public static function isInt($number)
  {
    return is_int($number);
  }

  /**
   * Permet de savoir si un nombre est un nombre à virgule flotante
   *
   * @param  integer  $number  Nombre à tester
   * @return boolean           TRUE si le nombre est un nombre à virgule flotante
   */
  public static function isFloat($number)
  {
    return is_float($number);
  }

  /**
   * Permet de savoir si une chaine est un nombre
   *
   * @param  string  $string  Chaîne à tester
   * @return boolean          TRUE si la chaîne est un nombre
   */
  public static function isNumber($string)
  {
    return !is_nan($string);
  }

  /**
   * Fonction exponentielle
   *
   * @param  integer  $number  Nombre sur lequel appliquer l'exponentielle
   * @return integer           Résultat de la fonction exponentielle
   */
  public static function exp($number)
  {
    return exp((float) $number);
  }

  /**
   * Fonction exponentielle-1
   *
   * @param  integer  $number  Nombre sur lequel appliquer l'exponentielle61
   * @return integer           Résultat de la fonction exponentielle-1
   */
  public static function expM1($number)
  {
    return expm1((float) $number);
  }

  /**
   * Mettre un nombre à une puissance donnée
   *
   * @param  integer  $number    Nombre
   * @param  integer  $exponent  (optional) Exposant (2 par défaut)
   * @return integer             Résultat
   */
  public static function power($number, $exponent = 2)
  {
    return pow((float) $number, $exponent);
  }

  /**
   * Effectue la racine nième d'un nombre
   *
   * @param  integer  $number    Nombre
   * @param  integer  $exponent  (optional) Exposant (2 par défaut)
   * @return integer             Résultat
   */
  public static function square($number, $exponent = 2)
  {
    return pow((float) $number, (1 / $exponent));
  }

  /**
   * Transforme une chaîne en nombre (si transformable)
   *
   * @param  string   $string  Chaîne de caractères représentant un nombre
   * @return integer           Nombre résultant de la conversion
   */
  public static function toNumber($string)
  {
    return (float) StringTools::replaceFirst($string, ',', '.');
  }

}


<?php namespace Ordercloud\Entities\Products;

use JsonSerializable;

class ProductOption extends ProductAddon implements JsonSerializable
{
    /**
     * @var array|ProductOptionSet[]
     * @reflectType Ordercloud\Entities\Products\ProductOptionSet
     */
    private $unlockOptionSets;
    /**
     * @var array|ProductExtraSet[]
     * @reflectType Ordercloud\Entities\Products\ProductExtraSet
     */
    private $unlockExtraSets;
    /**
     * @reflectName setDefault
     * @var bool
     */
    private $default;
    /**
     * @var bool
     */
    private $itemsPluFlatMappingEnabled;

    /**
     * @param int                      $id
     * @param string                   $name
     * @param string                   $description
     * @param float                    $price
     * @param boolean                  $enabled
     * @param array|ProductOptionSet[] $unlockOptionSets
     * @param array|ProductExtraSet[]  $unlockExtraSets
     * @param bool                     $default
     * @param bool                     $itemsPluFlatMappingEnabled
     */
    public function __construct($id, $name, $description, $price, $enabled, array $unlockOptionSets = [], array $unlockExtraSets = [], $default, $itemsPluFlatMappingEnabled)
    {
        parent::__construct($id, $name, $description, $price, $enabled);
        $this->unlockOptionSets = $unlockOptionSets ?: [];
        $this->unlockExtraSets = $unlockExtraSets ?: [];
        $this->default = $default;
        $this->itemsPluFlatMappingEnabled = $itemsPluFlatMappingEnabled;
    }

    /**
     * @return bool
     */
    public function hasUnlockOptionSets()
    {
        return ! empty($this->unlockOptionSets);
    }

    /**
     * @return array|ProductOptionSet[]
     */
    public function getUnlockOptionSets()
    {
        return $this->unlockOptionSets;
    }

    /**
     * @return bool
     */
    public function hasUnlockExtraSets()
    {
        return ! empty($this->unlockExtraSets);
    }

    /**
     * @return array|ProductExtraSet[]
     */
    public function getUnlockExtraSets()
    {
        return $this->unlockExtraSets;
    }

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

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

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

        $json['unlockOptionSets'] = $this->getUnlockOptionSets();
        $json['unlockExtraSets'] = $this->getUnlockExtraSets();
        $json['default'] = $this->isDefault();
        $json['itemsPluFlatMappingEnabled'] = $this->isItemsPluFlatMappingEnabled();

        return $json;
    }
}


<?php

namespace Colorium\Orm;

use Colorium\Orm\Contract\BuilderInterface;
use Colorium\Orm\Contract\QueryInterface;

trait Model
{

    /**
     * @id
     * @var int
     */
    public $id;

    /**
     * Add record
     *
     * @return int
     */
    public function add()
    {
        $id = Hub::add(static::class, $this);
        if($id) {
            $this->id = $id;
            return $id;
        }

        return false;
    }

    /**
     * Edit record
     *
     * @return int
     */
    public function edit()
    {
        if(!$this->id) {
            return false;
        }

        $where = ['id' => $this->id];
        return Hub::edit(static::class, $this, $where);
    }

    /**
     * Save record
     *
     * @return int
     */
    public function save()
    {
        return $this->id ? $this->edit() : $this->add();
    }

    /**
     * Delete record
     *
     * @return int
     */
    public function drop()
    {
        if(!$this->id) {
            return false;
        }

        $where['id'] = $this->id;
        return Hub::drop(static::class, $where);
    }

    /**
     * Fetch many result
     *
     * @param array $where
     * @param array $sort
     * @return object[]
     */
    public static function fetch(array $where = [], array $sort = [])
    {
        return Hub::fetch(static::class, $where, $sort);
    }

    /**
     * Fetch one result
     *
     * @param array $where
     * @return object
     */
    public static function one(array $where = [])
    {
        return Hub::one(static::class, $where);
    }

    /**
     * Generate query
     *
     * @return QueryInterface
     */
    public static function query()
    {
        return Hub::query(static::class);
    }

    /**
     * Generate builder
     *
     * @return BuilderInterface
     */
    public static function builder()
    {
        return Hub::builder(static::class);
    }

}