<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Database\Ddd;

use Closure;
use InvalidArgumentException;
use Leevel\Database\IConnect;
use Throwable;

/**
 * 工作单元.
 * 工作单元大量参考了 Doctrine2 以及 Java Bean 的实现和设计.
 * 最早基于 .NET 里面关于领域驱动设计代码实现，工作单元、仓储等概念均来源于此.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.10.14
 * @since 2018.10 参考 Doctrine2 进行一次重构
 * @since v1.0.0-beta.1@2019.04.25 增加前置后置的概念
 *
 * @version 1.0
 */
class UnitOfWork implements IUnitOfWork
{
    /**
     * 根实体.
     *
     * @var \Leevel\Database\Ddd\IEntity
     */
    protected $rootEntity;

    /**
     * 注入的新建实体.
     *
     * @var array
     */
    protected $entityCreates = [];

    /**
     * 注入的不存在则新建否则更新实体.
     *
     * @var array
     */
    protected $entityReplaces = [];

    /**
     * 注入的更新实体.
     *
     * @var array
     */
    protected $entityUpdates = [];

    /**
     * 注入的删除实体.
     *
     * @var array
     */
    protected $entityDeletes = [];

    /**
     * 注入的新建实体到前置区域的标识.
     *
     * @var array
     */
    protected $createsFlagBefore = [];

    /**
     * 注入的不存在则新建否则更新实体到前置区域的标识.
     *
     * @var array
     */
    protected $replacesFlagBefore = [];

    /**
     * 注入的更新实体到前置区域的标识.
     *
     * @var array
     */
    protected $updatesFlagBefore = [];

    /**
     * 注入的删除实体到前置区域的标识.
     *
     * @var array
     */
    protected $deletesFlagBefore = [];

    /**
     * 注入的新建实体到主区域的标识.
     *
     * @var array
     */
    protected $createsFlag = [];

    /**
     * 注入的不存在则新建否则更新实体到主区域的标识.
     *
     * @var array
     */
    protected $replacesFlag = [];

    /**
     * 注入的更新实体到主区域的标识.
     *
     * @var array
     */
    protected $updatesFlag = [];

    /**
     * 注入的删除实体到主区域的标识.
     *
     * @var array
     */
    protected $deletesFlag = [];

    /**
     * 注入的新建实体到后置区域的标识.
     *
     * @var array
     */
    protected $createsFlagAfter = [];

    /**
     * 注入的不存在则新建否则更新实体到后置区域的标识.
     *
     * @var array
     */
    protected $replacesFlagAfter = [];

    /**
     * 注入的更新实体到后置区域的标识.
     *
     * @var array
     */
    protected $updatesFlagAfter = [];

    /**
     * 注入的删除实体到后置区域的标识.
     *
     * @var array
     */
    protected $deletesFlagAfter = [];

    /**
     * 实体当前状态
     *
     * @var array
     */
    protected $entityStates = [];

    /**
     * 响应回调.
     *
     * @var \Closure[]
     */
    protected $onCallbacks = [];

    /**
     * 工作单元是否关闭.
     *
     * @var bool
     */
    protected $closed = false;

    /**
     * 构造函数.
     */
    public function __construct()
    {
        $this->rootEntity = new class() extends Entity {
            const TABLE = '';
            const ID = null;
            const AUTO = null;
            const STRUCT = [];

            public function setter(string $prop, $value): IEntity
            {
                $this->{$this->prop($prop)} = $value;

                return $this;
            }

            public function getter(string $prop)
            {
                return $this->{$this->prop($prop)};
            }
        };
    }

    /**
     * 是否处于协程上下文.
     *
     * @return bool
     */
    public static function coroutineContext(): bool
    {
        return true;
    }

    /**
     * 创建一个工作单元.
     *
     * @param \Leevel\Database\Ddd\IEntity $rootEntity
     * @param mixed                        $connect
     *
     * @return static
     */
    public static function make(IEntity $rootEntity = null, $connect = null): IUnitOfWork
    {
        return new static($rootEntity, $connect);
    }

    /**
     * 执行数据库事务.
     */
    public function flush(): void
    {
        $this->validateClosed();

        if (!($this->entityCreates ||
            $this->entityUpdates ||
            $this->entityDeletes ||
            $this->entityReplaces)) {
            return;
        }

        $this->beginTransaction();

        try {
            $this->handleRepository();
            $this->commit();
        } catch (Throwable $e) {
            $this->close();
            $this->rollBack();

            throw $e;
        }
    }

    /**
     * 保持实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param string                       $method
     *
     * @return $this
     */
    public function persistBefore(IEntity $entity, string $method = 'save'): IUnitOfWork
    {
        return $this->persistEntity('Before', $entity, $method);
    }

    /**
     * 保持实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param string                       $method
     *
     * @return $this
     */
    public function persist(IEntity $entity, string $method = 'save'): IUnitOfWork
    {
        return $this->persistEntity('', $entity, $method);
    }

    /**
     * 保持实体到后置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param string                       $method
     *
     * @return $this
     */
    public function persisteAfter(IEntity $entity, string $method = 'save'): IUnitOfWork
    {
        return $this->persistEntity('After', $entity, $method);
    }

    /**
     * 移除实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function removeBefore(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        return $this->removeEntity('Before', $entity, $priority);
    }

    /**
     * 移除实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function remove(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        return $this->removeEntity('', $entity, $priority);
    }

    /**
     * 移除实体到后置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function removeAfter(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        return $this->removeEntity('After', $entity, $priority);
    }

    /**
     * 注册新建实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function createBefore(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->createEntity($entity);

        $id = spl_object_id($entity);
        $this->createsFlagBefore[$id] = $priority;

        return $this;
    }

    /**
     * 注册新建实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function create(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->createEntity($entity);

        $id = spl_object_id($entity);
        $this->createsFlag[$id] = $priority;

        return $this;
    }

    /**
     * 注册新建实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function createAfter(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->createEntity($entity);

        $id = spl_object_id($entity);
        $this->createsFlagAfter[$id] = $priority;

        return $this;
    }

    /**
     * 实体是否已经注册新增.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return bool
     */
    public function created(IEntity $entity, int $priority = 500): bool
    {
        return isset($this->entityCreates[spl_object_id($entity)]);
    }

    /**
     * 注册更新实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function updateBefore(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->updateEntity($entity);

        $id = spl_object_id($entity);
        $this->updatesFlagBefore[$id] = $priority;

        return $this;
    }

    /**
     * 注册更新实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function update(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->updateEntity($entity);

        $id = spl_object_id($entity);
        $this->updatesFlag[$id] = $priority;

        return $this;
    }

    /**
     * 注册更新实体到后置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function updateAfter(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->updateEntity($entity);

        $id = spl_object_id($entity);
        $this->updatesFlagAfter[$id] = $priority;

        return $this;
    }

    /**
     * 实体是否已经注册更新.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return bool
     */
    public function updated(IEntity $entity): bool
    {
        return isset($this->entityUpdates[spl_object_id($entity)]);
    }

    /**
     * 注册不存在则新增否则更新实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priorit
     *
     * @return $this
     */
    public function replaceBefore(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->replaceEntity($entity);

        $id = spl_object_id($entity);
        $this->replacesFlagBefore[$id] = $priority;

        return $this;
    }

    /**
     * 注册不存在则新增否则更新实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priorit
     *
     * @return $this
     */
    public function replace(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->replaceEntity($entity);

        $id = spl_object_id($entity);
        $this->replacesFlag[$id] = $priority;

        return $this;
    }

    /**
     * 注册不存在则新增否则更新实体到后置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priorit
     *
     * @return $this
     */
    public function replaceAfter(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->replaceEntity($entity);

        $id = spl_object_id($entity);
        $this->replacesFlagAfter[$id] = $priority;

        return $this;
    }

    /**
     * 实体是否已经注册不存在则新增否则更新.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return bool
     */
    public function replaced(IEntity $entity): bool
    {
        return isset($this->entityReplaces[spl_object_id($entity)]);
    }

    /**
     * 注册删除实体到前置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function deleteBefore(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->deleteEntity($entity);

        $id = spl_object_id($entity);
        $this->deletesFlagBefore[$id] = $priority;

        return $this;
    }

    /**
     * 注册删除实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function delete(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->deleteEntity($entity);

        $id = spl_object_id($entity);
        $this->deletesFlag[$id] = $priority;

        return $this;
    }

    /**
     * 注册删除实体到后置区域.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    public function deleteAfter(IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $this->deleteEntity($entity);

        $id = spl_object_id($entity);
        $this->deletesFlagAfter[$id] = $priority;

        return $this;
    }

    /**
     * 实体是否已经注册删除.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return bool
     */
    public function deleted(IEntity $entity): bool
    {
        return isset($this->entityDeletes[spl_object_id($entity)]);
    }

    /**
     * 实体是否已经注册.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return bool
     */
    public function registered(IEntity $entity): bool
    {
        $id = spl_object_id($entity);

        return isset($this->entityCreates[$id]) ||
            isset($this->entityUpdates[$id]) ||
            isset($this->entityDeletes[$id]) ||
            isset($this->entityReplaces[$id]);
    }

    /**
     * 刷新实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return $this
     */
    public function refresh(IEntity $entity): IUnitOfWork
    {
        $this->validateClosed();

        if (self::STATE_MANAGED !== $this->getEntityState($entity)) {
            $e = sprintf('Entity `%s` was not managed.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        $this->repository($entity)->refresh($entity);

        return $this;
    }

    /**
     * 注册实体为管理状态.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     */
    public function registerManaged(IEntity $entity): void
    {
        $this->entityStates[spl_object_id($entity)] = self::STATE_MANAGED;
    }

    /**
     * 设置根实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $rootEntity
     */
    public function setRootEntity(IEntity $rootEntity): void
    {
        $this->rootEntity = $rootEntity;
    }

    /**
     * 设置连接.
     *
     * @param mixed $connect
     */
    public function setConnect($connect): void
    {
        $this->rootEntity->withConnect($connect);
    }

    /**
     * 返回数据库连接.
     *
     * @return \Leevel\Database\IConnect
     */
    public function connect(): IConnect
    {
        return $this->rootEntity->databaseConnect();
    }

    /**
     * 启动事物.
     */
    public function beginTransaction(): void
    {
        $this->connect()->beginTransaction();
    }

    /**
     * 事务回滚.
     */
    public function rollBack(): void
    {
        $this->connect()->rollBack();
    }

    /**
     * 事务自动提交.
     */
    public function commit(): void
    {
        $this->connect()->commit();
    }

    /**
     * 执行数据库事务.
     *
     * @param \Closure $action
     *
     * @return mixed
     */
    public function transaction(Closure $action)
    {
        $this->beginTransaction();

        try {
            $result = $action($this);

            $this->flush();
            $this->commit();

            return $result;
        } catch (Throwable $e) {
            $this->rollBack();
            $this->close();

            throw $e;
        }
    }

    /**
     * 清理工作单元.
     */
    public function clear(): void
    {
        $this->entityCreates = [];
        $this->entityUpdates = [];
        $this->entityDeletes = [];
        $this->entityReplaces = [];
        $this->entityStates = [];
        $this->onCallbacks = [];
        $this->createsFlagBefore = [];
        $this->createsFlag = [];
        $this->createsFlagAfter = [];
        $this->replacesFlagBefore = [];
        $this->replacesFlag = [];
        $this->replacesFlagAfter = [];
        $this->updatesFlagBefore = [];
        $this->updatesFlag = [];
        $this->updatesFlagAfter = [];
        $this->deletesFlagBefore = [];
        $this->deletesFlag = [];
        $this->deletesFlagAfter = [];
    }

    /**
     * 关闭.
     */
    public function close(): void
    {
        $this->clear();
        $this->closed = true;
    }

    /**
     * 响应回调.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param \Closure                     $callbacks
     */
    public function on(IEntity $entity, Closure $callbacks): void
    {
        $this->onCallbacks[spl_object_id($entity)][] = $callbacks;
    }

    /**
     * 取得实体仓储.
     *
     * @param \Leevel\Database\Ddd\IEntity|string $entity
     *
     * @return \Leevel\Database\Ddd\IRepository
     */
    public function repository($entity): IRepository
    {
        if (is_string($entity)) {
            $entity = new $entity();
        }

        if (defined(get_class($entity).'::REPOSITORY')) {
            $name = $entity::REPOSITORY;
            $repository = new $name($entity);
        } else {
            $repository = new Repository($entity);
        }

        return $repository;
    }

    /**
     * 取得实体状态.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $defaults
     *
     * @return int
     */
    public function getEntityState(IEntity $entity, ?int $defaults = null): int
    {
        $id = spl_object_id($entity);

        if (isset($this->entityStates[$id])) {
            return $this->entityStates[$id];
        }

        if (null !== $defaults) {
            return $defaults;
        }

        if (!$entity->id()) {
            return self::STATE_NEW;
        }

        return self::STATE_DETACHED;
    }

    /**
     * 保持实体.
     *
     * @param string                       $position
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param string                       $method
     *
     * @return $this
     */
    protected function persistEntity(string $position, IEntity $entity, string $method = 'save'): IUnitOfWork
    {
        $this->validateClosed();

        $id = spl_object_id($entity);

        $entityState = $this->getEntityState($entity, self::STATE_NEW);

        switch ($entityState) {
            case self::STATE_MANAGED:
                break;
            case self::STATE_NEW:
                $this->persistNewEntry($position, $method, $entity);

                break;
            case self::STATE_REMOVED:
                if (isset($this->entityDeletes[$id])) {
                    unset($this->entityDeletes[$id]);

                    foreach (['deletesFlagBefore', 'deletesFlag', 'deletesFlagAfter'] as $flag) {
                        if (isset($this->{$flag}[$id])) {
                            unset($this->{$flag}[$id]);
                        }
                    }
                }

                $this->entityStates[$id] = self::STATE_MANAGED;

                break;
            case self::STATE_DETACHED:
            default:
                $e = sprintf('Detached entity `%s` cannot be persist.', get_class($entity));

                throw new InvalidArgumentException($e);
        }

        return $this;
    }

    /**
     * 移除实体.
     *
     * @param string                       $position
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    protected function removeEntity(string $position, IEntity $entity, int $priority = 500): IUnitOfWork
    {
        $id = spl_object_id($entity);

        $entityState = $this->getEntityState($entity);

        switch ($entityState) {
            case self::STATE_NEW:
            case self::STATE_REMOVED:
                break;
            case self::STATE_MANAGED:
                $this->{'delete'.$position}($entity, $priority);

                break;
             case self::STATE_DETACHED:
             default:
                $e = sprintf('Detached entity `%s` cannot be remove.', get_class($entity));

                throw new InvalidArgumentException($e);
        }

        return $this;
    }

    /**
     * 注册新建实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param int                          $priority
     *
     * @return $this
     */
    protected function createEntity(IEntity $entity): IUnitOfWork
    {
        $this->validateClosed();

        $id = spl_object_id($entity);

        if (isset($this->entityUpdates[$id])) {
            $e = sprintf('Updated entity `%s` cannot be added for create.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityDeletes[$id])) {
            $e = sprintf('Deleted entity `%s` cannot be added for create.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityReplaces[$id])) {
            $e = sprintf('Replaced entity `%s` cannot be added for create.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityCreates[$id])) {
            $e = sprintf('Entity `%s` cannot be added for twice.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        $this->entityCreates[$id] = $entity;
        $this->entityStates[$id] = self::STATE_MANAGED;

        return $this;
    }

    /**
     * 注册更新实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return $this
     */
    protected function updateEntity(IEntity $entity): IUnitOfWork
    {
        $this->validateClosed();

        $id = spl_object_id($entity);

        if (!$entity->id()) {
            $e = sprintf('Entity `%s` has no identity for update.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityDeletes[$id])) {
            $e = sprintf('Deleted entity `%s` cannot be added for update.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityCreates[$id])) {
            $e = sprintf('Created entity `%s` cannot be added for update.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityReplaces[$id])) {
            $e = sprintf('Replaced entity `%s` cannot be added for update.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityUpdates[$id])) {
            $e = sprintf('Entity `%s` cannot be updated for twice.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        $this->entityUpdates[$id] = $entity;
        $this->entityStates[$id] = self::STATE_MANAGED;

        return $this;
    }

    /**
     * 注册不存在则新增否则更新实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return $this
     */
    protected function replaceEntity(IEntity $entity): IUnitOfWork
    {
        $this->validateClosed();

        $id = spl_object_id($entity);

        if (isset($this->entityDeletes[$id])) {
            $e = sprintf('Deleted entity `%s` cannot be added for replace.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityCreates[$id])) {
            $e = sprintf('Created entity `%s` cannot be added for replace.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityUpdates[$id])) {
            $e = sprintf('Updated entity `%s` cannot be added for replace.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityReplaces[$id])) {
            $e = sprintf('Entity `%s` cannot be replaced for twice.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        $this->entityReplaces[$id] = $entity;
        $this->entityStates[$id] = self::STATE_MANAGED;

        return $this;
    }

    /**
     * 注册删除实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return $this
     */
    protected function deleteEntity(IEntity $entity): IUnitOfWork
    {
        $this->validateClosed();

        $id = spl_object_id($entity);

        if (isset($this->entityCreates[$id])) {
            unset($this->entityCreates[$id], $this->entityStates[$id]);

            foreach (['createsFlagBefore', 'createsFlag', 'createsFlagAfter'] as $flag) {
                if (isset($this->{$flag}[$id])) {
                    unset($this->{$flag}[$id]);
                }
            }

            return $this;
        }

        if (isset($this->entityReplaces[$id])) {
            unset($this->entityReplaces[$id]);

            foreach (['replacesFlagBefore', 'replacesFlag', 'replacesFlagAfter'] as $flag) {
                if (isset($this->{$flag}[$id])) {
                    unset($this->{$flag}[$id]);
                }
            }

            if (!$entity->id()) {
                return $this;
            }
        }

        if (!$entity->id()) {
            $e = sprintf('Entity `%s` has no identity for delete.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if (isset($this->entityUpdates[$id])) {
            unset($this->entityUpdates[$id]);

            foreach (['updatesFlagBefore', 'updatesFlag', 'updatesFlagAfter'] as $flag) {
                if (isset($this->{$flag}[$id])) {
                    unset($this->{$flag}[$id]);
                }
            }
        }

        if (isset($this->entityDeletes[$id])) {
            $e = sprintf('Entity `%s` cannot be deleted for twice.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        $this->entityDeletes[$id] = $entity;
        $this->entityStates[$id] = self::STATE_REMOVED;

        return $this;
    }

    /**
     * 响应仓储.
     */
    protected function handleRepository(): void
    {
        foreach (['Before', '', 'After'] as $position) {
            foreach (['creates', 'replaces', 'updates', 'deletes'] as $type) {
                $flag = $type.'Flag'.$position;

                if ($this->{$flag}) {
                    $flags = $this->{$flag};
                    asort($flags);

                    foreach ($flags as $id => $_) {
                        $entity = $this->{'entity'.ucfirst($type)}[$id];
                        $this->repository($entity)->{substr($type, 0, -1)}($entity);

                        if (isset($this->onCallbacks[$id])) {
                            foreach ($this->onCallbacks[$id] as $c) {
                                $c($entity, $this);
                            }
                        }

                        $this->entityStates[$id] = self::STATE_DETACHED;
                    }
                }
            }
        }

        $oldStates = $this->entityStates;
        $this->clear();
        $this->entityStates = $oldStates;
    }

    /**
     * 处理持久化.
     *
     * @param string                       $position
     * @param string                       $method
     * @param \Leevel\Database\Ddd\IEntity $entity
     */
    protected function persistNewEntry(string $position, string $method, IEntity $entity): void
    {
        switch (strtolower($method)) {
            case 'create':
                $this->{'create'.$position}($entity);

                break;
            case 'update':
                $this->{'update'.$position}($entity);

                break;
            case 'replace':
                $this->{'replace'.$position}($entity);

                break;
            case 'save':
            default:
                $ids = $entity->id();

                if (is_array($ids)) {
                    $this->{'replace'.$position}($entity);
                } else {
                    if (empty($ids)) {
                        $this->{'create'.$position}($entity);
                    } else {
                        $this->{'update'.$position}($entity);
                    }
                }

                break;
        }
    }

    /**
     * 校验工作单元是否关闭.
     */
    protected function validateClosed(): void
    {
        if ($this->closed) {
            throw new InvalidArgumentException('Unit of work has closed.');
        }
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Page;

use JsonSerializable;
use Leevel\Support\IArray;
use Leevel\Support\IHtml;
use Leevel\Support\IJson;
use RuntimeException;

/**
 * 分页处理.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.07.14
 *
 * @version 1.0
 */
class Page implements IPage, IJson, IArray, IHtml, JsonSerializable
{
    /**
     * 总记录数量.
     *
     * @var int
     */
    protected $totalRecord;

    /**
     * 每页分页数量.
     *
     * @var int
     */
    protected $perPage;

    /**
     * 当前分页页码
     *
     * @var int
     */
    protected $currentPage;

    /**
     * 总页数.
     *
     * @var int
     */
    protected $totalPage;

    /**
     * 分页开始位置.
     *
     * @var int
     */
    protected $pageStart;

    /**
     * 分页结束位置.
     *
     * @var int
     */
    protected $pageEnd;

    /**
     * 缓存 url 地址
     *
     * @var string
     */
    protected $cachedUrl;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'page'          => 'page',
        'range'         => 2,
        'render'        => 'defaults',
        'render_option' => [],
        'url'           => null,
        'parameter'     => [],
        'fragment'      => null,
    ];

    /**
     * 构造函数.
     *
     * @param int   $currentPage
     * @param int   $perPage
     * @param int   $totalRecord
     * @param array $option
     */
    public function __construct(int $currentPage, ?int $perPage = null, ?int $totalRecord = null, array $option = [])
    {
        $this->currentPage = $currentPage;
        $this->perPage = $perPage;
        $this->totalRecord = $totalRecord;

        $this->option = array_merge($this->option, $option);
    }

    /**
     * 转化为字符串.
     *
     * @return string
     */
    public function __toString(): string
    {
        return (string) $this->render();
    }

    /**
     * 设置配置.
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return $this
     */
    public function setOption(string $name, $value): IPage
    {
        $this->option[$name] = $value;

        return $this;
    }

    /**
     * 转化输出 HTML.
     *
     * @return string
     */
    public function toHtml(): string
    {
        return (string) $this->render();
    }

    /**
     * 追加分页条件.
     *
     * @param string $key
     * @param string $value
     *
     * @return $this
     */
    public function append(string $key, string $value): IPage
    {
        return $this->addParameter($key, $value);
    }

    /**
     * 批量追加分页条件.
     *
     * @param array $values
     *
     * @return $this
     */
    public function appends(array $values): IPage
    {
        foreach ($values as $key => $value) {
            $this->addParameter($key, $value);
        }

        return $this;
    }

    /**
     * 设置分页条件.
     *
     * @param array $parameter
     *
     * @return $this
     */
    public function parameter(array $parameter): IPage
    {
        return $this->setOption('parameter', $parameter);
    }

    /**
     * 添加分页条件.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return $this
     */
    public function addParameter(string $key, $value): IPage
    {
        $tmp = $this->option['parameter'];
        $tmp[$key] = $value;

        $this->setOption('parameter', $tmp);

        return $this;
    }

    /**
     * 设置渲染参数.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return $this
     */
    public function renderOption(string $key, $value): IPage
    {
        $tmp = $this->option['render_option'];
        $tmp[$key] = $value;

        $this->setOption('render_option', $tmp);

        return $this;
    }

    /**
     * 批量设置渲染参数.
     *
     * @param string $key
     * @param string $value
     *
     * @return $this
     */
    public function renderOptions(array $option): IPage
    {
        foreach ($option as $key => $value) {
            $this->renderOption($key, $value);
        }

        return $this;
    }

    /**
     * 设置 url.
     *
     * @param null|string $url
     *
     * @return $this
     */
    public function url(?string $url = null): IPage
    {
        return $this->setOption('url', $url);
    }

    /**
     * 设置 render.
     *
     * @param null|string $render
     *
     * @return $this
     */
    public function setRender(?string $render = null): IPage
    {
        return $this->setOption('render', $render);
    }

    /**
     * 获取 render.
     *
     * @return null|string
     */
    public function getRender(): string
    {
        return $this->option['render'] ?: static::RENDER;
    }

    /**
     * 设置 range.
     *
     * @param null|int $range
     *
     * @return $this
     */
    public function range(?int $range = null): IPage
    {
        return $this->setOption('range', $range);
    }

    /**
     * 获取 range.
     *
     * @return int
     */
    public function getRange(): int
    {
        return $this->option['range'] ?
            (int) ($this->option['range']) :
            static::RANGE;
    }

    /**
     * 设置 url 描点.
     *
     * @param null|string $fragment
     *
     * @return $this
     */
    public function fragment(?string $fragment = null): IPage
    {
        return $this->setOption('fragment', $fragment);
    }

    /**
     * 获取 url 描点.
     *
     * @return null|string
     */
    public function getFragment(): ?string
    {
        return $this->option['fragment'];
    }

    /**
     * 设置每页分页数量.
     *
     * @param int perPage
     *
     * @return $this
     */
    public function perPage(int $perPage): IPage
    {
        $this->perPage = $perPage;

        return $this;
    }

    /**
     * 返回每页数量.
     *
     * @return int
     */
    public function getPerPage(): int
    {
        if (null === $this->perPage) {
            $this->perPage = static::PER_PAGE;
        }

        return $this->perPage;
    }

    /**
     * 设置分页名字.
     *
     * @param string $pageName
     *
     * @return $this
     */
    public function pageName(string $pageName): IPage
    {
        return $this->setOption('page', $pageName);
    }

    /**
     * 获取分页名字.
     *
     * @return string
     */
    public function getPageName(): string
    {
        return $this->option['page'];
    }

    /**
     * 返回总记录数量.
     *
     * @return null|int
     */
    public function getTotalRecord(): ?int
    {
        return $this->totalRecord;
    }

    /**
     * 是否为无限分页.
     *
     * @return bool
     */
    public function isTotalMacro(): bool
    {
        return $this->getTotalRecord() === static::MACRO;
    }

    /**
     * 取得第一个记录的编号.
     *
     * @return int
     */
    public function getFromRecord(): int
    {
        return ($this->getCurrentPage() - 1) * $this->getPerPage();
    }

    /**
     * 取得最后一个记录的编号.
     *
     * @return null|int
     */
    public function getToRecord(): ?int
    {
        if (!$this->canTotalRender()) {
            return null;
        }

        $to = $this->getFromRecord() + $this->getPerPage();

        return $to <= $this->getTotalRecord() ? $to : $this->getTotalRecord();
    }

    /**
     * 设置当前分页.
     *
     * @param int $page
     */
    public function currentPage(int $page): void
    {
        $this->currentPage = $page;
    }

    /**
     * 返回当前分页.
     *
     * @return int
     */
    public function getCurrentPage(): int
    {
        return $this->currentPage;
    }

    /**
     * 返回分页视图开始页码
     *
     * @return int
     */
    public function getPageStart(): int
    {
        if (null !== $this->pageStart) {
            return $this->pageStart;
        }

        $this->pageStart = $this->getCurrentPage() - $this->getRange();

        if ($this->pageStart < $this->getRange() * 2) {
            $this->pageStart = 1;
        }

        return $this->pageStart;
    }

    /**
     * 返回分页视图结束页码
     *
     * @return int
     */
    public function getPageEnd(): int
    {
        if (null !== $this->pageEnd) {
            return $this->pageEnd;
        }

        $this->pageEnd = $this->getCurrentPage() + $this->getRange();

        if (1 === $this->getPageStart()) {
            $this->pageEnd = $this->getRange() * 2 + 2;
        }

        if ($this->getTotalPage() &&
            $this->pageEnd > $this->getTotalPage()) {
            $this->pageEnd = $this->getTotalPage();
        }

        return $this->pageEnd;
    }

    /**
     * 返回总分页数量.
     *
     * @return null|int
     */
    public function getTotalPage(): ?int
    {
        if (null !== $this->totalPage || !$this->getTotalRecord()) {
            return $this->totalPage;
        }

        $this->totalPage = (int) (
            ceil($this->getTotalRecord() / $this->getPerPage())
        );

        return $this->totalPage;
    }

    /**
     * 是否渲染 total.
     *
     * @return bool
     */
    public function canTotalRender(): bool
    {
        return null !== $this->getTotalRecord() &&
            !$this->isTotalMacro();
    }

    /**
     * 是否渲染 first.
     *
     * @return bool
     */
    public function canFirstRender(): bool
    {
        return $this->getTotalPage() > 1 &&
            $this->getCurrentPage() >= ($this->getRange() * 2 + 2);
    }

    /**
     * 返回渲染 first.prev.
     *
     * @return int
     */
    public function parseFirstRenderPrev(): int
    {
        return $this->getCurrentPage() - ($this->getRange() * 2 + 1);
    }

    /**
     * 是否渲染 prev.
     *
     * @return bool
     */
    public function canPrevRender(): bool
    {
        return (null === $this->getTotalPage() || $this->getTotalPage() > 1) &&
            1 !== $this->getCurrentPage();
    }

    /**
     * 返回渲染 prev.prev.
     *
     * @return int
     */
    public function parsePrevRenderPrev(): int
    {
        return $this->getCurrentPage() - 1;
    }

    /**
     * 是否渲染 main.
     *
     * @return bool
     */
    public function canMainRender(): bool
    {
        return $this->getTotalPage() > 1;
    }

    /**
     * 是否渲染 next.
     *
     * @return bool
     */
    public function canNextRender(): bool
    {
        return null === $this->getTotalPage() ||
            ($this->getTotalPage() > 1 &&
                $this->getCurrentPage() !== $this->getTotalPage());
    }

    /**
     * 是否渲染 last.
     *
     * @return bool
     */
    public function canLastRender(): bool
    {
        return $this->getTotalPage() > 1 &&
            $this->getCurrentPage() !== $this->getTotalPage() &&
            $this->getTotalPage() > $this->getPageEnd();
    }

    /**
     * 是否渲染 last.
     *
     * @return bool
     */
    public function canLastRenderNext(): bool
    {
        return $this->getTotalPage() > ($this->getPageEnd() + 1);
    }

    /**
     * 返回渲染 last.next.
     *
     * @return int
     */
    public function parseLastRenderNext(): int
    {
        $next = $this->getCurrentPage() +
            $this->getRange() * 2 + 1;

        if (!$this->isTotalMacro() &&
            $next > $this->getTotalPage()) {
            $next = $this->getTotalPage();
        }

        return $next;
    }

    /**
     * 替换分页变量.
     *
     * @param int|string $page
     *
     * @return string
     */
    public function pageReplace($page): string
    {
        return str_replace([
            urlencode('{page}'),
            '{page}',
        ], $page, $this->getUrl());
    }

    /**
     * 渲染分页.
     *
     * @param null|\Leevel\Page\IRender|string $render
     * @param array                            $option
     *
     * @return string
     */
    public function render($render = null, array $option = []): string
    {
        $option = array_merge($this->option['render_option'], $option);

        if (null === $render || is_string($render)) {
            $render = $render ?: $this->getRender();
            $render = __NAMESPACE__.'\\'.ucfirst($render);
            $render = new $render($this);
        } elseif (!$render instanceof IRender) {
            throw new RuntimeException('Unsupported render type.');
        }

        $result = $render->render($option);

        $this->cachedUrl = null;

        return $result;
    }

    /**
     * 对象转数组.
     *
     * @return array
     */
    public function toArray(): array
    {
        return [
            'per_page'     => $this->getPerPage(),
            'current_page' => $this->getCurrentPage(),
            'total_page'   => $this->getTotalPage(),
            'total_record' => $this->getTotalRecord(),
            'total_macro'  => $this->isTotalMacro(),
            'from'         => $this->getFromRecord(),
            'to'           => $this->getToRecord(),
        ];
    }

    /**
     * 实现 JsonSerializable::jsonSerialize.
     *
     * @return array
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

    /**
     * 对象转 JSON.
     *
     * @param int $option
     *
     * @return string
     */
    public function toJson($option = null): string
    {
        if (null === $option) {
            $option = JSON_UNESCAPED_UNICODE;
        }

        return json_encode($this->jsonSerialize(), $option);
    }

    /**
     * 分析分页 url 地址.
     * {page} 表示自定义分页变量替换.
     *
     * @return string
     */
    protected function getUrl(): string
    {
        if (null !== $this->cachedUrl) {
            return $this->cachedUrl;
        }

        $url = (string) ($this->option['url']);

        $parameter = $this->option['parameter'];

        if (isset($parameter[$this->option['page']])) {
            unset($parameter[$this->option['page']]);
        }

        if (false === strpos($url, '{page}')) {
            $parameter[$this->option['page']] = '{page}';
        }

        $this->cachedUrl = $url.
            (false === strpos($url, '?') ? '?' : '&').
            http_build_query($parameter, '', '&');

        return $this->cachedUrl .= $this->buildFragment();
    }

    /**
     * 创建描点.
     *
     * @return string
     */
    protected function buildFragment(): string
    {
        return $this->getFragment() ? '#'.$this->getFragment() : '';
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Router\Match;

use Leevel\Http\IRequest;
use Leevel\Router\IRouter;

/**
 * 注解路由匹配.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.15
 *
 * @version 1.0
 */
class Annotation extends Match implements IMatch
{
    /**
     * 匹配变量.
     *
     * @var array
     */
    protected $matchedVars = [];

    /**
     * 匹配数据项.
     *
     * @param \Leevel\Router\IRouter $router
     * @param \Leevel\Http\IRequest  $request
     *
     * @return array
     */
    public function matche(IRouter $router, IRequest $request): array
    {
        $this->setRouterAndRequest($router, $request);

        return $this->matchMain();
    }

    /**
     * 主匹配.
     *
     * @return array
     */
    protected function matchMain(): array
    {
        if (!($routers = $this->router->getRouters())) {
            return [];
        }

        // 匹配路由请求方法
        if (false === ($routers = $this->matcheMethod($routers))) {
            return [];
        }

        // 匹配 PathInfo
        $pathInfo = $this->matchePathInfo();

        // 静态路由匹配
        if (false !== ($result = $this->matcheStatic($routers))) {
            return $result;
        }

        // 匹配首字母
        if (false === ($routers = $this->matcheFirstLetter($pathInfo, $routers))) {
            return [];
        }

        // 匹配分组
        $routers = $this->matcheGroups($pathInfo, $routers);

        // 路由匹配
        if (false !== ($result = $this->matcheRegexGroups($routers))) {
            return $result;
        }

        return [];
    }

    /**
     * 匹配路由方法.
     *
     * @param array $routers
     *
     * @return array|false
     */
    protected function matcheMethod(array $routers)
    {
        $method = strtolower($this->request->getMethod());

        return $routers[$method] ?? false;
    }

    /**
     * 匹配静态路由.
     *
     * @param array $routers
     *
     * @return array|false
     */
    protected function matcheStatic(array $routers)
    {
        $pathInfo = $this->getPathInfo();

        if (isset($routers['static'], $routers['static'][$pathInfo])) {
            $routers = $routers['static'][$pathInfo];

            return $this->matcheSuccessed($routers);
        }

        return false;
    }

    /**
     * 匹配首字母.
     *
     * @param string $pathInfo
     * @param array  $routers
     *
     * @return array|false
     */
    protected function matcheFirstLetter(string $pathInfo, array $routers)
    {
        return $routers[$pathInfo[1]] ?? false;
    }

    /**
     * 匹配路由分组.
     *
     * @param string $pathInfo
     * @param array  $routers
     *
     * @return array
     */
    protected function matcheGroups(string $pathInfo, array $routers): array
    {
        $matchGroup = false;

        foreach ($this->router->getGroups() as $group) {
            if (0 === strpos($pathInfo, $group)) {
                $routers = $routers[$group];
                $matchGroup = true;

                break;
            }
        }

        if (false === $matchGroup) {
            $routers = $routers['_'] ?? [];
        }

        return $routers;
    }

    /**
     * 匹配路由正则分组.
     *
     * @param array $routers
     *
     * @return array|false
     */
    protected function matcheRegexGroups(array $routers)
    {
        $pathInfo = $this->getPathInfo();

        foreach ($routers['regex'] as $key => $regex) {
            if (!preg_match($regex, $pathInfo, $matches)) {
                continue;
            }

            $matchedRouter = $routers['map'][$key][count($matches)];
            $routers = $routers[$matchedRouter];
            $matcheVars = $this->matcheVariable($routers, $matches);

            return $this->matcheSuccessed($routers, $matcheVars);
        }

        return false;
    }

    /**
     * 注解路由匹配成功处理.
     *
     * @param array $routers
     * @param array $matcheVars
     *
     * @return array
     */
    protected function matcheSuccessed(array $routers, array $matcheVars = []): array
    {
        // 协议匹配
        if (!empty($routers['scheme']) &&
            false === $this->matcheScheme($routers['scheme'])) {
            return [];
        }

        // 域名匹配
        if (false === ($domainVars = $this->matcheDomain($routers))) {
            return [];
        }

        // 未绑定
        if (!$routers['bind']) {
            return [];
        }

        $result = [];

        $result[IRouter::BIND] = $routers['bind'];
        $result[IRouter::APP] = $this->findApp($routers['bind']);

        $result['params'] = [];

        // 域名匹配参数 {subdomain}.{domain}
        if ($domainVars) {
            $result['params'] = $domainVars;
        }

        // 路由匹配参数 /v1/pet/{id}
        if ($matcheVars) {
            $result['params'] = array_merge($result['params'], $matcheVars);
        }

        // 额外参数 ['extend1' => 'foo']
        if (isset($routers['params']) && is_array($routers['params'])) {
            $result['params'] = array_merge($result['params'], $routers['params']);
        }

        $result[IRouter::PARAMS] = $result['params'];
        unset($result['params']);

        // 中间件
        if (isset($routers['middlewares'])) {
            $this->middlewares = $this->mergeMiddlewares($this->middlewares, $routers['middlewares']);
        }

        $result[IRouter::MIDDLEWARES] = $this->middlewares;

        // 匹配的变量
        $result[IRouter::VARS] = $this->matchedVars;

        return $result;
    }

    /**
     * 协议匹配.
     *
     * @param string $scheme
     *
     * @return bool
     */
    protected function matcheScheme(string $scheme): bool
    {
        if ($scheme && $this->request->getScheme() !== $scheme) {
            return false;
        }

        return true;
    }

    /**
     * 域名匹配.
     *
     * @param array $routers
     *
     * @return array|bool
     */
    protected function matcheDomain(array $routers)
    {
        $domainVars = [];

        if (!empty($routers['domain'])) {
            // ignore the port
            $host = $this->request->getHost();

            if (!empty($routers['domain_regex'])) {
                if (!preg_match($routers['domain_regex'], $host, $matches)) {
                    return false;
                }

                array_shift($matches);

                foreach ($routers['domain_var'] as $var) {
                    $value = array_shift($matches);

                    $domainVars[$var] = $value;
                    $this->addVariable($var, $value);
                }
            } elseif ($host !== $routers['domain']) {
                return false;
            }
        }

        return $domainVars;
    }

    /**
     * 变量匹配处理.
     *
     * @param array $routers
     * @param array $matches
     *
     * @return array
     */
    protected function matcheVariable(array $routers, array $matches): array
    {
        $result = [];

        array_shift($matches);

        foreach ($routers['var'] as $key => $var) {
            $value = $matches[$key];

            $result[$var] = $matches[$key];
            $this->addVariable($var, $matches[$key]);
        }

        return $result;
    }

    /**
     * 添加解析变量.
     *
     * @param string $name
     * @param mixed  $value
     */
    protected function addVariable(string $name, $value): void
    {
        $this->matchedVars[$name] = $value;
    }

    /**
     * 查找 App.
     *
     * @param string $path
     *
     * @return string
     */
    protected function findApp(string $path): string
    {
        $tmp = explode('\\', $path);

        return array_shift($tmp);
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Database\Ddd;

use Closure;
use InvalidArgumentException;
use Leevel\Collection\Collection;
use Leevel\Database\Ddd\Relation\Relation;
use Leevel\Database\Select as DatabaseSelect;
use function Leevel\Support\Arr\normalize;
use Leevel\Support\Arr\normalize;
use function Leevel\Support\Str\contains;
use Leevel\Support\Str\contains;
use function Leevel\Support\Str\starts_with;
use Leevel\Support\Str\starts_with;

/**
 * 模型实体查询.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.07.10
 *
 * @version 1.0
 */
class Select
{
    /**
     * 模型实体.
     *
     * @var \Leevel\Database\Ddd\IEntity
     */
    protected $entity;

    /**
     * 查询.
     *
     * @var \Leevel\Database\Select
     */
    protected $select;

    /**
     * 关联预载入.
     *
     * @var array
     */
    protected $preLoads = [];

    /**
     * 构造函数.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     */
    public function __construct(IEntity $entity)
    {
        $this->entity = $entity;

        $this->select = $this->entity->databaseSelect();
    }

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        $result = $this->select->{$method}(...$args);

        if ($result instanceof DatabaseSelect) {
            return $this;
        }

        if (!$this->preLoads) {
            return $result;
        }

        if (is_array($result) &&
            isset($result[DatabaseSelect::PAGE]) &&
            true === $result[DatabaseSelect::PAGE]) {
            $result[1] = $this->preLoadResult($result[1]);
        } else {
            $result = $this->preLoadResult($result);
        }

        return $result;
    }

    /**
     * 获取模型实体.
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function entity(): IEntity
    {
        return $this->entity;
    }

    /**
     * 添加预载入的关联.
     *
     * @param array $relation
     *
     * @return $this
     */
    public function eager(array $relation): self
    {
        $this->preLoads = array_merge(
            $this->preLoads,
            $this->parseWithRelation($relation)
        );

        return $this;
    }

    /**
     * 尝试解析结果预载.
     *
     * @param mixed $result
     *
     * @return mixed
     */
    public function preLoadResult($result)
    {
        list($result, $type) = $this->conversionToEntitys($result);

        if ($type) {
            $result = $this->preLoadRelation($result);

            if ('entity' === $type) {
                $result = reset($result);
            } elseif ('collection' === $type) {
                $result = new Collection($result);
            }
        }

        return $result;
    }

    /**
     * 通过主键查找模型实体.
     *
     * @param int   $id
     * @param array $column
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function find(int $id, array $column = ['*']): IEntity
    {
        return $this->select
            ->where($this->entity->singlePrimaryKey(), '=', $id)
            ->setColumns($column)
            ->findOne();
    }

    /**
     * 根据主键查找模型实体.
     *
     * @param array $ids
     * @param array $column
     *
     * @return \Leevel\Collection\Collection
     */
    public function findMany(array $ids, array $column = ['*']): Collection
    {
        if (empty($ids)) {
            return $this->entity->collection();
        }

        return $this->select
            ->whereIn($this->entity->singlePrimaryKey(), $ids)
            ->setColumns($column)
            ->findAll();
    }

    /**
     * 通过主键查找模型实体，未找到则抛出异常.
     *
     * @param int   $id
     * @param array $column
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function findOrFail(int $id, array $column = ['*']): IEntity
    {
        $result = $this->find($id, $column);

        if (null !== $result->__get($this->entity->singlePrimaryKey())) {
            return $result;
        }

        throw (new EntityNotFoundException())
            ->setEntity(get_class($this->entity));
    }

    /**
     * 从模型实体中软删除数据.
     *
     * @return int
     */
    public function softDelete(): int
    {
        $this->entity->__set($this->deleteAtColumn(), $time = date('Y-m-d H:i:s'));

        $this->entity->handleEvent(IEntity::BEFORE_SOFT_DELETE_EVENT);

        $num = $this->entity->update()->flush();

        $this->entity->handleEvent(IEntity::AFTER_SOFT_DELETE_EVENT);

        return $num;
    }

    /**
     * 根据主键 ID 删除模型实体.
     *
     * @param array $ids
     * @param mixed $id
     *
     * @return int
     */
    public function softDestroy(array $ids): int
    {
        $count = 0;

        $instance = $this->entity->make();
        $entitys = $instance
            ->whereIn($instance->singlePrimaryKey(), $ids)
            ->findAll();

        foreach ($entitys as $value) {
            if ($value->softDelete()) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * 恢复软删除的模型实体.
     *
     * @return int
     */
    public function softRestore(): int
    {
        $this->entity->handleEvent(IEntity::BEFORE_SOFT_RESTORE_EVENT);

        $this->entity->__set($this->deleteAtColumn(), null);

        $num = $this->entity->update()->flush();

        $this->entity->handleEvent(IEntity::AFTER_SOFT_RESTORE_EVENT);

        return $num;
    }

    /**
     * 获取不包含软删除的数据.
     *
     * @return \Leevel\Database\Select
     */
    public function withoutSoftDeleted(): DatabaseSelect
    {
        return $this->select->whereNull($this->deleteAtColumn());
    }

    /**
     * 获取只包含软删除的数据.
     *
     * @return \Leevel\Database\Select
     */
    public function onlySoftDeleted(): DatabaseSelect
    {
        return $this->select->whereNotNull($this->deleteAtColumn());
    }

    /**
     * 检查模型实体是否已经被软删除了.
     *
     * @return bool
     */
    public function softDeleted(): bool
    {
        return null !== $this->entity->__get($this->deleteAtColumn());
    }

    /**
     * 获取软删除字段.
     *
     * @return string
     */
    public function deleteAtColumn(): string
    {
        if (defined(get_class($this->entity).'::DELETE_AT')) {
            $deleteAt = $this->entity::DELETE_AT;
        } else {
            $deleteAt = 'delete_at';
        }

        if (!$this->entity->hasField($deleteAt)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Entity `%s` soft delete field `%s` was not found.',
                    get_class($this->entity), $deleteAt
                )
            );
        }

        return $deleteAt;
    }

    /**
     * 查询范围.
     *
     * @param mixed $scope
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function scope($scope): IEntity
    {
        $scopeSelect = $this->select;

        $args = func_get_args();
        array_shift($args);
        array_unshift($args, $scopeSelect);

        if ($scope instanceof Closure) {
            $scope(...$args);
            $this->entity->withScopeSelect($scopeSelect);
        } else {
            foreach (normalize($scope) as $value) {
                $value = 'scope'.ucfirst($value);

                if (method_exists($this->entity, $value)) {
                    $this->entity->{$value}(...$args);
                    $this->entity->withScopeSelect($scopeSelect);
                }
            }
        }

        return $this->entity;
    }

    /**
     * 预载入模型实体.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     *
     * @return array
     */
    protected function preLoadRelation(array $entitys): array
    {
        foreach ($this->preLoads as $name => $condition) {
            if (false === strpos($name, '.')) {
                $entitys = $this->loadRelation($entitys, $name, $condition);
            }
        }

        return $entitys;
    }

    /**
     * 取得关联模型实体.
     *
     * @param string $name
     *
     * @return \leevel\Database\Ddd\Relation\Relation
     */
    protected function getRelation(string $name): Relation
    {
        $relation = Relation::withoutRelationCondition(function () use ($name) {
            return $this->entity->{$name}();
        });

        $nested = $this->nestedRelation($name);

        if (count($nested) > 0) {
            $relation->getSelect()->eager($nested);
        }

        return $relation;
    }

    /**
     * 尝试取得嵌套关联.
     *
     * @param string $relation
     *
     * @return array
     */
    protected function nestedRelation(string $relation): array
    {
        $nested = [];

        foreach ($this->preLoads as $name => $condition) {
            if ($this->isNested($name, $relation)) {
                $nested[substr($name, strlen($relation.'.'))] = $condition;
            }
        }

        return $nested;
    }

    /**
     * 判断是否存在嵌套关联.
     *
     * @param string $name
     * @param string $relation
     *
     * @return bool
     */
    protected function isNested(string $name, string $relation): bool
    {
        return contains($name, '.') && starts_with($name, $relation.'.');
    }

    /**
     * 格式化预载入关联.
     *
     * @param array $relation
     *
     * @return array
     */
    protected function parseWithRelation(array $relation): array
    {
        $arr = [];

        foreach ($relation as $name => $condition) {
            if (is_numeric($name)) {
                list($name, $condition) = [
                    $condition,
                    function () {
                    },
                ];
            }

            $arr = $this->parseNestedWith($name, $arr);

            $arr[$name] = $condition;
        }

        return $arr;
    }

    /**
     * 解析嵌套关联.
     *
     * @param string $name
     * @param array  $result
     *
     * @return array
     */
    protected function parseNestedWith(string $name, array $result): array
    {
        $progress = [];

        foreach (explode('.', $name) as $segment) {
            $progress[] = $segment;

            if (!isset($result[$last = implode('.', $progress)])) {
                $result[$last] = function () {
                };
            }
        }

        return $result;
    }

    /**
     * 转换结果到模型实体类型.
     *
     * @param mixed $result
     *
     * @return array
     */
    protected function conversionToEntitys($result): array
    {
        $type = '';

        if ($result instanceof Collection) {
            $arr = [];

            foreach ($result as $entity) {
                $arr[] = $entity;
            }

            $result = $arr;
            $type = 'collection';
        } elseif (is_object($result) && $result instanceof IEntity) {
            $result = [$result];
            $type = 'entity';
        }

        return [$result, $type];
    }

    /**
     * 关联数据设置到模型实体上.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     * @param string                         $name
     * @param \Closure                       $condition
     *
     * @return array
     */
    protected function loadRelation(array $entitys, string $name, Closure $condition): array
    {
        $relation = $this->getRelation($name);

        $relation->preLoadCondition($entitys);

        call_user_func($condition, $relation);

        return $relation->matchPreLoad($entitys, $relation->getPreLoad(), $name);
    }
}

// import fn.
class_exists(normalize::class);
class_exists(contains::class);
class_exists(starts_with::class);


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Event;

use Leevel\Di\IContainer;

/**
 * 事件.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.17
 *
 * @version 1.0
 */
class Dispatch implements IDispatch
{
    /**
     * 应用容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * 注册的监听器.
     *
     * @var array
     */
    protected $listeners = [];

    /**
     * 通配符的监听器.
     *
     * @var array
     */
    protected $wildcards = [];

    /**
     * 创建一个事件解析器.
     *
     * @param \Leevel\Di\IContainer $container
     */
    public function __construct(IContainer $container)
    {
        $this->container = $container;
    }

    /**
     * 执行一个事件.
     *
     * @param object|string $event
     * @param array         $params
     */
    public function handle($event, ...$params): void
    {
        if (is_object($event)) {
            $name = get_class($event);
        } else {
            $name = $event;

            // This may return object or string
            $event = $this->container->make($event);
        }

        array_unshift($params, $event);

        $listeners = $this->get($name);

        if (!$listeners) {
            return;
        }

        ksort($listeners);

        $listeners = array_reduce($listeners, function (array $result, array $value) {
            return array_merge($result, $value);
        }, []);

        $this->makeSubject($listeners)->notify(...$params);
    }

    /**
     * 注册监听器.
     *
     * @param array|object|string $event
     * @param mixed               $listener
     * @param int                 $priority
     */
    public function register($event, $listener, int $priority = 500): void
    {
        $event = is_object($event) ? [$event] : (array) $event;

        foreach ($event as $item) {
            $item = $this->normalizeEvent($item);

            if (false !== strpos($item, '*')) {
                $this->wildcards[$item][$priority][] = $listener;
            } else {
                $this->listeners[$item][$priority][] = $listener;
            }
        }
    }

    /**
     * 获取一个事件监听器.
     *
     * @param object|string $event
     *
     * @return array
     */
    public function get($event): array
    {
        $listeners = [];

        $event = $this->normalizeEvent($event);

        if (isset($this->listeners[$event])) {
            $listeners = $this->listeners[$event];
        }

        foreach ($this->wildcards as $key => $item) {
            $key = $this->prepareRegexForWildcard($key);

            if (preg_match($key, $event, $res)) {
                foreach ($item as $priority => $value) {
                    $listeners[$priority] = array_merge($listeners[$priority] ?? [], $value);
                }
            }
        }

        return $listeners;
    }

    /**
     * 判断事件监听器是否存在.
     *
     * @param object|string $event
     *
     * @return bool
     */
    public function has($event): bool
    {
        return [] !== $this->get($event);
    }

    /**
     * 删除一个事件所有监听器.
     *
     * @param object|string $event
     */
    public function delete($event): void
    {
        $event = $this->normalizeEvent($event);

        if (isset($this->listeners[$event])) {
            unset($this->listeners[$event]);
        }

        if (isset($this->wildcards[$event])) {
            unset($this->wildcards[$event]);
        }
    }

    /**
     * 创建监听器观察者角色主体.
     *
     * @param string $listeners
     *
     * @return \Leevel\Event\Subject
     */
    protected function makeSubject(array $listeners): Subject
    {
        $subject = new Subject($this->container);

        foreach ($listeners as $item) {
            $subject->register($item);
        }

        return $subject;
    }

    /**
     * 格式化事件名字.
     *
     * @param object|string $event
     *
     * @return string
     */
    protected function normalizeEvent($event): string
    {
        return is_object($event) ? get_class($event) : $event;
    }

    /**
     * 通配符正则.
     *
     * @param string $regex
     *
     * @return string
     */
    protected function prepareRegexForWildcard(string $regex): string
    {
        $regex = preg_quote($regex, '/');
        $regex = '/^'.str_replace('\*', '(\S*)', $regex).'$/';

        return $regex;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Protocol;

use InvalidArgumentException;
use Leevel\Di\IContainer;
use SplStack;

/**
 * 对象池.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.12.14
 *
 * @version 1.0
 */
class Pool implements IPool
{
    /**
     * IOC 容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * 对象池.
     *
     * @var array
     */
    protected $pools = [];

    /**
     * 构造函数.
     *
     * @param \Leevel\Di\IContainer $container
     */
    public function __construct(IContainer $container)
    {
        $this->container = $container;
    }

    /**
     * 获取一个对象.
     *
     * @param string $className
     * @param array  $args
     *
     * @return \Object
     */
    public function get(string $className, ...$args)
    {
        $this->valid($className);
        $className = $this->normalize($className);
        $pool = $this->pool($className);

        if ($pool->count()) {
            $obj = $pool->shift();

            if (is_callable([$obj, '__construct'])) {
                $obj->__construct(...$args);
            }

            if (is_callable([$obj, 'construct'])) {
                $obj->construct();
            }

            return $obj;
        }

        return $this->container->make($className, $args);
    }

    /**
     * 返还一个对象.
     *
     * @param \Object $obj
     */
    public function back($obj): void
    {
        if (method_exists($obj, 'destruct')) {
            $obj->destruct();
        }

        $className = $this->normalize(get_class($obj));
        $pool = $this->pool($className);
        $pool->push($obj);
    }

    /**
     * 获取对象栈.
     *
     * @param string $className
     *
     * @return \SplStack
     */
    public function pool(string $className): SplStack
    {
        $this->valid($className);
        $className = $this->normalize($className);
        $pool = $this->pools[$className] ?? null;

        if (null !== $pool) {
            return $pool;
        }

        return $this->pools[$className] = new SplStack();
    }

    /**
     * 获取对象池数据.
     *
     * @return array
     */
    public function getPools(): array
    {
        return $this->pools;
    }

    /**
     * 校验类是否存在.
     *
     * @param string $className
     */
    protected function valid(string $className): void
    {
        if (!class_exists($className)) {
            throw new InvalidArgumentException(sprintf('Class `%s` was not found.', $className));
        }
    }

    /**
     * 统一去掉前面的斜杠.
     *
     * @param string $name
     *
     * @return string
     */
    protected function normalize(string $name): string
    {
        return ltrim($name, '\\');
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\View\Provider;

use Leevel\Di\IContainer;
use Leevel\Di\Provider;
use Leevel\View\Compiler;
use Leevel\View\Manager;
use Leevel\View\Parser;

/**
 * view 服务提供者.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.12
 *
 * @version 1.0
 */
class Register extends Provider
{
    /**
     * 注册服务.
     */
    public function register(): void
    {
        $this->viewViews();
        $this->viewView();
        $this->viewCompiler();
        $this->viewParser();
    }

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'view.views' => [
                'Leevel\\View\\Manager',
            ],
            'view.view' => [
                'Leevel\\View\\View',
                'Leevel\\View\\IView',
            ],
            'view.compiler' => [
                'Leevel\\View\\Compiler',
                'Leevel\\View\\ICompiler',
            ],
            'view.parser' => [
                'Leevel\\View\\Parser',
                'Leevel\\View\\IParser',
            ],
        ];
    }

    /**
     * 是否延迟载入.
     *
     * @return bool
     */
    public static function isDeferred(): bool
    {
        return true;
    }

    /**
     * 注册 view.views 服务.
     */
    protected function viewViews(): void
    {
        $this->container->singleton('view.views', function (IContainer $container) {
            return new Manager($container);
        });
    }

    /**
     * 注册 view.view 服务.
     */
    protected function viewView(): void
    {
        $this->container->singleton('view.view', function (IContainer $container) {
            return $container['view.views']->connect();
        });
    }

    /**
     * 注册 view.compiler 服务.
     */
    protected function viewCompiler(): void
    {
        $this->container->singleton('view.compiler', function (IContainer $container) {
            return new Compiler();
        });
    }

    /**
     * 注册 view.parser 服务.
     */
    protected function viewParser(): void
    {
        $this->container->singleton('view.parser', function (IContainer $container) {
            return (new Parser($container['view.compiler']))
                ->registerCompilers()
                ->registerParsers();
        });
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Throttler\Middleware;

use Closure;
use Leevel\Http\IRequest;
use Leevel\Kernel\Exception\TooManyRequestsHttpException;
use Leevel\Throttler\IThrottler;

/**
 * throttler 中间件.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.10
 *
 * @version 1.0
 */
class Throttler
{
    /**
     * throttler.
     *
     * @var \Leevel\Throttler\IThrottler
     */
    protected $throttler;

    /**
     * 构造函数.
     *
     * @param \Leevel\Throttler\IThrottler $throttler
     */
    public function __construct(IThrottler $throttler)
    {
        $this->throttler = $throttler;
    }

    /**
     * 请求
     *
     * @param \Closure              $next
     * @param \Leevel\Http\IRequest $request
     * @param int                   $limit
     * @param int                   $time
     */
    public function handle(Closure $next, IRequest $request, int $limit = 60, int $time = 60): void
    {
        $rateLimiter = $this->throttler->

        setRequest($request)->

        create(null, $limit, $time);

        if ($rateLimiter->attempt()) {
            $e = new TooManyRequestsHttpException('Too many attempts.');
            $e->setHeaders($rateLimiter->header());

            throw $e;
        }

        $next($request);
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Log;

use Leevel\Event\IDispatch;
use Leevel\Manager\Manager as Managers;

/**
 * log 入口.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.15
 *
 * @version 1.0
 */
class Manager extends Managers
{
    /**
     * 取得配置命名空间.
     *
     * @return string
     */
    protected function normalizeOptionNamespace(): string
    {
        return 'log';
    }

    /**
     * 创建连接对象
     *
     * @param object $connect
     *
     * @return object
     */
    protected function createConnect(object $connect): object
    {
        return new Log($connect,
            $this->getCommonOption(),
            $this->container->make(IDispatch::class)
        );
    }

    /**
     * 创建 file 日志驱动.
     *
     * @param array $options
     *
     * @return \Leevel\Log\File
     */
    protected function makeConnectFile(array $options = []): File
    {
        return new File(
            $this->normalizeConnectOption('file', $options)
        );
    }

    /**
     * 创建 syslog 日志驱动.
     *
     * @param array $options
     *
     * @return \Leevel\Log\Syslog
     */
    protected function makeConnectSyslog(array $options = []): Syslog
    {
        return new Syslog(
            $this->normalizeConnectOption('syslog', $options)
        );
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Kernel;

use Leevel\Console\Application;
use Leevel\Console\Load;
use Leevel\Console\Make;
use Leevel\Http\Request;
use Leevel\Kernel\Bootstrap\LoadI18n;
use Leevel\Kernel\Bootstrap\LoadOption;
use Leevel\Kernel\Bootstrap\RegisterRuntime;
use Leevel\Kernel\Bootstrap\TraverseProvider;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * 命令行内核执行.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.05.04
 *
 * @version 1.0
 */
abstract class KernelConsole implements IKernelConsole
{
    /**
     * 应用.
     *
     * @var \Leevel\Kernel\IApp
     */
    protected $app;

    /**
     * 命令行应用.
     *
     * @var \Leevel\Console\Application
     */
    protected $consoleApplication;

    /**
     * 应用初始化执行.
     *
     * @var array
     */
    protected $bootstraps = [
        LoadOption::class,
        LoadI18n::class,
        RegisterRuntime::class,
        TraverseProvider::class,
    ];

    /**
     * 构造函数.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function __construct(IApp $app)
    {
        $this->app = $app;
    }

    /**
     * 响应命令行请求
     *
     * @param \Symfony\Component\Console\Input\InputInterface   $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @return int
     */
    public function handle(InputInterface $input = null, OutputInterface $output = null): int
    {
        $this->registerBaseService();

        $this->bootstrap();

        $this->setGlobalReplace();

        $this->loadCommands();

        return $this->getConsoleApplication()->run($input, $output);
    }

    /**
     * 执行结束
     *
     * @param int                                             $status
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @codeCoverageIgnore
     */
    public function terminate(int $status, InputInterface $input = null): void
    {
    }

    /**
     * 初始化.
     *
     * @codeCoverageIgnore
     */
    public function bootstrap(): void
    {
        $this->app->bootstrap($this->bootstraps);
    }

    /**
     * 返回应用.
     *
     * @return \Leevel\Kernel\IApp
     */
    public function getApp(): IApp
    {
        return $this->app;
    }

    /**
     * 取得命令行应用.
     *
     * @return \Leevel\Console\Application
     * @codeCoverageIgnore
     */
    protected function getConsoleApplication(): Application
    {
        if ($this->consoleApplication) {
            return $this->consoleApplication;
        }

        return $this->consoleApplication = new Application($this->app, $this->app->version());
    }

    /**
     * 注册基础服务
     */
    protected function registerBaseService(): void
    {
        $this->app->instance('request', Request::createFromGlobals());
    }

    /**
     * 载入.
     */
    protected function loadCommands(): void
    {
        $commands = $this->normalizeCommands($this->getCommands());

        $this->getConsoleApplication()->normalizeCommands($commands);
    }

    /**
     * 设置全局替换.
     */
    protected function setGlobalReplace(): void
    {
        Make::setGlobalReplace(
            $this->app['option']->get('console\\template') ?: []
        );
    }

    /**
     * 整理命令.
     *
     * @param array $commands
     *
     * @return array
     */
    protected function normalizeCommands(array $commands): array
    {
        $result = $tmp = [];

        foreach ($commands as $item) {
            $tmp[class_exists($item) ? 'class' : 'namespace'][] = $item;
        }

        if (isset($tmp['class'])) {
            $result = $tmp['class'];
        }

        if (isset($tmp['namespace'])) {
            $result = array_merge($result, $this->getCommandsWithNamespace($tmp['namespace']));
        }

        return $result;
    }

    /**
     * 整理命令.
     *
     * @param array $namespaces
     *
     * @return array
     */
    protected function getCommandsWithNamespace(array $namespaces): array
    {
        $data = [];

        foreach ($namespaces as $item) {
            $data[$item] = $this->app->namespacePath($item.'\\index');
        }

        return (new Load())->addNamespace($data)->loadData();
    }

    /**
     * 获取系统命令.
     *
     * @return array
     */
    protected function getCommands(): array
    {
        return $this->app['option']->get('_composer.commands');
    }
}