<?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 ArrayAccess;
use InvalidArgumentException;
use JsonSerializable;
use Leevel\Collection\Collection;
use Leevel\Database\Ddd\Relation\BelongsTo;
use Leevel\Database\Ddd\Relation\HasMany;
use Leevel\Database\Ddd\Relation\HasOne;
use Leevel\Database\Ddd\Relation\ManyMany;
use Leevel\Database\Ddd\Relation\Relation;
use Leevel\Database\ReplaceException;
use Leevel\Database\Select as DatabaseSelect;
use Leevel\Event\IDispatch;
use Leevel\I18n\Helper\gettext;
use function Leevel\I18n\Helper\gettext as __;
use Leevel\Support\IArray;
use Leevel\Support\IJson;
use function Leevel\Support\Str\camelize;
use Leevel\Support\Str\camelize;
use function Leevel\Support\Str\un_camelize;
use Leevel\Support\Str\un_camelize;

/**
 * 模型实体 Object Relational Mapping.
 * 为最大化避免 getter setter 属性与系统冲突，getFoo 修改为 getterFoo，setBar 修改为 setterBar
 * 系统自身的属性均加前缀 leevel，设置以 with 开头.
 * ORM 主要基于妖怪大神的 QeePHP V2 设计灵感，查询器基于这个版本构建.
 * 例外参照了 Laravel 关联模型实现设计.
 * Doctrine 和 Java Hibernate 中关于 getter setter 的设计
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.27
 * @since 2018.10 进行一次大规模重构
 * @since 1.0.0-beta.1 2019.04.24 getFoo 修改为 getterFoo，setBar 修改为 setterBar
 * @see https://github.com/dualface/qeephp2_x
 * @see https://github.com/laravel/framework
 * @see https://github.com/doctrine/doctrine2
 * @see http://hibernate.org/
 *
 * @version 1.0
 */
abstract class Entity implements IEntity, IArray, IJson, JsonSerializable, ArrayAccess
{
    /**
     * 此模型实体的连接名称.
     *
     * @var mixed
     */
    protected $leevelConnect;

    /**
     * 已修改的模型实体属性.
     *
     * @var array
     */
    protected $leevelChangedProp = [];

    /**
     * 黑白名单.
     *
     * @var array
     */
    protected $leevelBlackWhites = [
        'construct_prop' => [
            'white' => [],
            'black' => [],
        ],
        'create_prop' => [
            'white' => [],
            'black' => [],
        ],
        'update_prop' => [
            'white' => [],
            'black' => [],
        ],
        'show_prop' => [
            'white' => [],
            'black' => [],
        ],
        'create_fill' => [
            'white' => [],
            'black' => [],
        ],
        'update_fill' => [
            'white' => [],
            'black' => [],
        ],
    ];

    /**
     * 指示对象是否对应数据库中的一条记录.
     *
     * @var bool
     */
    protected $leevelNewed = true;

    /**
     * Replace 模式.
     * 先插入出现主键重复.
     * false 表示非 replace 模式，其它值表示 replace 模式附带的 fill 数据.
     *
     * @var mixed
     */
    protected $leevelReplace = false;

    /**
     * 多对多关联中间实体.
     *
     * @var \Leevel\Database\Ddd\Entity
     */
    protected $leevelRelationMiddle;

    /**
     * 作用域查询对象.
     *
     * @var \Leevel\Database\Select
     */
    protected $leevelScopeSelect;

    /**
     * 持久化基础层
     *
     * @var \Closure
     */
    protected $leevelFlush;

    /**
     * 即将持久化数据.
     *
     * @var array
     */
    protected $leevelFlushData;

    /**
     * 是否已经持久化数据.
     *
     * @var bool
     */
    protected $leevelFlushed = false;

    /**
     * 模型实体事件处理器.
     *
     * @var \Leevel\Event\IDispatch
     */
    protected static $leevelDispatch;

    /**
     * 缓存驼峰法命名属性.
     *
     * @var array
     */
    protected static $leevelCamelize = [];

    /**
     * 缓存下划线命名属性.
     *
     * @var array
     */
    protected static $leevelUnCamelize = [];

    /**
     * 缓存 ENUM 格式化数据.
     *
     * @var array
     */
    protected static $leevelEnums = [];

    /**
     * 构造函数.
     *
     * @param array $data
     * @param bool  $fromStorage
     */
    public function __construct(array $data = [], bool $fromStorage = false)
    {
        $className = static::class;

        foreach (['TABLE', 'ID', 'AUTO', 'STRUCT'] as $item) {
            if (!defined($className.'::'.$item)) {
                $e = sprintf('The entity const %s was not defined.', $item);

                throw new InvalidArgumentException($e);
            }
        }

        foreach (static::STRUCT as $field => $v) {
            foreach ([
                'construct_prop', 'show_prop', 'create_prop',
                'update_prop', 'create_fill', 'update_fill',
            ] as $type) {
                foreach (['black', 'white'] as $bw) {
                    if (isset($v[$type.'_'.$bw]) && true === $v[$type.'_'.$bw]) {
                        $this->leevelBlackWhites[$type][$bw][] = $field;
                    }
                }
            }
        }

        if ($fromStorage) {
            $this->leevelNewed = false;
        }

        if ($data) {
            foreach ($this->normalizeWhiteAndBlack($data, 'construct_prop') as $prop => $value) {
                if (isset($data[$prop])) {
                    $this->withPropValue($prop, $data[$prop], !$fromStorage, true);
                }
            }
        }
    }

    /**
     * 魔术方法获取.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function __get(string $prop)
    {
        return $this->offsetGet($prop);
    }

    /**
     * 更新属性值
     *
     * @param string $key
     * @param mixed  $value
     */
    public function __set(string $prop, $value): void
    {
        $this->offsetSet($prop, $value);
    }

    /**
     * 是否存在属性.
     *
     * @param string $prop
     *
     * @return bool
     */
    public function __isset(string $prop): bool
    {
        return $this->offsetExists($prop);
    }

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        // getter
        if (0 === strpos($method, 'getter')) {
            if (!method_exists($this, 'getter')) {
                $e = sprintf('The entity `%s` `%s` method was not defined.', static::class, 'getter');

                throw new InvalidArgumentException($e);
            }

            return $this->getter(lcfirst(substr($method, 6)));
        }

        // setter
        if (0 === strpos($method, 'setter')) {
            if (!method_exists($this, 'setter')) {
                $e = sprintf('The entity `%s` `%s` method was not defined.', static::class, 'setter');

                throw new InvalidArgumentException($e);
            }

            $this->setter(lcfirst(substr($method, 6)), $args[0] ?? null);

            return $this;
        }

        // relation
        try {
            $unCamelize = $this->normalize($method);

            if ($this->isRelation($unCamelize)) {
                return $this->loadRelation($unCamelize, true);
            }
        } catch (InvalidArgumentException $e) {
        }

        // 作用域
        if (method_exists($this, 'scope'.ucwords($method))) {
            array_unshift($args, $method);

            return $this->scope(...$args);
        }

        $this->handleEvent(static::BEFORE_FIND_EVENT);
        $this->handleEvent(static::BEFORE_SELECT_EVENT);

        $data = $this->select()->{$method}(...$args);

        if ($data instanceof Collection) {
            $this->handleEvent(static::AFTER_SELECT_EVENT, $data);
        } else {
            $this->handleEvent(static::AFTER_FIND_EVENT, $data);
        }

        return $data;
    }

    /**
     * call static.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public static function __callStatic(string $method, array $args)
    {
        return (new static())->{$method}(...$args);
    }

    /**
     * 将模型实体转化为 JSON.
     *
     * @return string
     */
    public function __toString(): string
    {
        return $this->toJson();
    }

    /**
     * 创建新的实例.
     *
     * @param array $data
     * @param bool  $fromStorage
     *
     * @return static
     */
    public static function make(array $data = [], bool $fromStorage = false): IEntity
    {
        return new static($data, $fromStorage);
    }

    /**
     * 批量修改属性.
     *
     * @param array $data
     *
     * @return $this
     */
    public function withProps(array $data): IEntity
    {
        foreach ($data as $prop => $value) {
            $this->offsetSet($prop, $value);
        }

        return $this;
    }

    /**
     * 自动判断快捷方式.
     *
     * @param array      $data
     * @param null|array $fill
     *
     * @return $this
     */
    public function save(array $data = [], array $fill = null): IEntity
    {
        $this->saveEntry('save', $data, $fill);

        return $this;
    }

    /**
     * 新增快捷方式.
     *
     * @param array      $data
     * @param null|array $fill
     *
     * @return $this
     */
    public function create(array $data = [], array $fill = null): IEntity
    {
        $this->saveEntry('create', $data, $fill);

        return $this;
    }

    /**
     * 更新快捷方式.
     *
     * @param array      $data
     * @param null|array $fill
     *
     * @return $this
     */
    public function update(array $data = [], array $fill = null): IEntity
    {
        $this->saveEntry('update', $data, $fill);

        return $this;
    }

    /**
     * replace 快捷方式.
     *
     * @param array      $data
     * @param null|array $fill
     *
     * @return $this
     */
    public function replace(array $data = [], array $fill = null): IEntity
    {
        $this->saveEntry('replace', $data, $fill);

        return $this;
    }

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

        $instance = new static();

        foreach ($instance->whereIn($instance->singlePrimaryKey(), $ids)->findAll() as $entity) {
            if ($entity->destroy()->flush()) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * 销毁模型实体.
     *
     * @return $this
     */
    public function destroy(): IEntity
    {
        if (null === $this->primaryKey()) {
            $e = sprintf('Entity %s has no primary key.', static::class);

            throw new InvalidArgumentException($e);
        }

        $this->leevelFlushed = false;

        $this->leevelFlush = function ($condition) {
            $this->handleEvent(static::BEFORE_DELETE_EVENT, $condition);

            $num = $this->metaConnect()->delete($condition);

            $this->handleEvent(static::AFTER_DELETE_EVENT);

            return $num;
        };

        $this->leevelFlushData = [$this->idCondition()];

        return $this;
    }

    /**
     * 数据持久化数据.
     *
     * @return mixed
     */
    public function flush()
    {
        if (!$this->leevelFlush || true === $this->leevelFlushed) {
            return;
        }

        try {
            $result = call_user_func_array($this->leevelFlush, $this->leevelFlushData);
        } catch (ReplaceException $e) {
            if (false === $this->leevelReplace) {
                throw $e;
            }

            $this->leevelFlush = null;
            $this->leevelFlushData = null;
            $this->updateReal($this->leevelReplace);
            $this->leevelReplace = false;

            return $this->flush();
        }

        $this->leevelFlush = null;
        $this->leevelFlushData = null;
        $this->leevelFlushed = true;

        $this->handleEvent(static::AFTER_SAVE_EVENT);

        return $result;
    }

    /**
     * 获取是否已经持久化数据.
     *
     * @return bool
     */
    public function flushed(): bool
    {
        return $this->leevelFlushed;
    }

    /**
     * 获取数据持久化数据.
     *
     * @return null|array
     */
    public function flushData(): ?array
    {
        return $this->leevelFlushData;
    }

    /**
     * 确定对象是否对应数据库中的一条记录.
     *
     * @return bool
     */
    public function newed(): bool
    {
        return $this->leevelNewed;
    }

    /**
     * 获取主键
     * 唯一标识符.
     *
     * @return mixed
     */
    public function id()
    {
        $result = [];

        foreach (($keys = $this->primaryKeys()) as $value) {
            if (!($tmp = $this->__get($value))) {
                continue;
            }

            $result[$value] = $tmp;
        }

        if (!$result) {
            return;
        }

        // 复合主键，但是数据不完整则忽略
        if (count($keys) > 1 && count($keys) !== count($result)) {
            return;
        }

        if (1 === count($result)) {
            $result = reset($result);
        }

        return $result;
    }

    /**
     * 从数据库重新读取当前对象的属性.
     */
    public function refresh(): void
    {
        $key = $this->primaryKey();

        if (null === $key) {
            $e = sprintf('Entity %s do not have primary key.', static::class);

            throw new InvalidArgumentException($e);
        }

        if (is_array($key)) {
            $map = $this->id();
        } else {
            $map = [$this->singlePrimaryKey(), $this->id()];
        }

        $data = $this
            ->metaConnect()
            ->select()
            ->where($map)
            ->findOne();

        foreach ($data as $k => $v) {
            $this->withPropValue($k, $v, false);
        }
    }

    /**
     * 返回关联数据.
     *
     * @param string $prop
     *
     * @return mixed
     */
    public function loadRelationProp(string $prop)
    {
        if ($result = $this->relationProp($prop)) {
            return $result;
        }

        return $this->loadDataFromRelation($prop);
    }

    /**
     * 是否为关联属性.
     *
     * @param string $prop
     *
     * @return bool
     */
    public function isRelation(string $prop): bool
    {
        $prop = $this->normalize($prop);

        $this->validate($prop);

        $struct = static::STRUCT[$prop];

        if (isset($struct[self::BELONGS_TO]) ||
           isset($struct[self::HAS_MANY]) ||
           isset($struct[self::HAS_ONE]) ||
           isset($struct[self::MANY_MANY])) {
            return true;
        }

        return false;
    }

    /**
     * 读取关联.
     *
     * @param string $prop
     *
     * @return \Leevel\Database\Ddd\Relation\Relation
     */
    public function loadRelation(string $prop): Relation
    {
        $prop = $this->normalize($prop);

        $this->validate($prop);

        $defined = static::STRUCT[$prop];

        if (isset($defined[self::BELONGS_TO])) {
            $this->validateRelationDefined($defined, ['source_key', 'target_key']);

            $relation = $this->belongsTo(
               $defined[self::BELONGS_TO],
               $defined['target_key'],
               $defined['source_key']
           );
        } elseif (isset($defined[self::HAS_MANY])) {
            $this->validateRelationDefined($defined, ['source_key', 'target_key']);

            $relation = $this->hasMany(
               $defined[self::HAS_MANY],
               $defined['target_key'],
               $defined['source_key']
           );
        } elseif (isset($defined[self::HAS_ONE])) {
            $this->validateRelationDefined($defined, ['source_key', 'target_key']);

            $relation = $this->hasOne(
               $defined[self::HAS_ONE],
               $defined['target_key'],
               $defined['source_key']
           );
        } elseif (isset($defined[self::MANY_MANY])) {
            $this->validateRelationDefined($defined, [
                'middle_entity', 'source_key', 'target_key',
                'middle_target_key', 'middle_source_key',
            ]);

            $relation = $this->ManyMany(
               $defined[self::MANY_MANY],
               $defined['middle_entity'],
               $defined['target_key'],
               $defined['source_key'],
               $defined['middle_target_key'],
               $defined['middle_source_key']
           );
        }

        if (isset($defined[self::SCOPE])) {
            call_user_func([$this, 'scope'.ucfirst($defined[self::SCOPE])], $relation);
        }

        return $relation;
    }

    /**
     * 取得关联数据.
     *
     * @param string $prop
     *
     * @return mixed
     */
    public function relationProp(string $prop)
    {
        $this->validate($prop);

        return $this->propGetter($prop);
    }

    /**
     * 设置关联数据.
     *
     * @param string $prop
     * @param mixed  $value
     */
    public function withRelationProp(string $prop, $value): void
    {
        $this->validate($prop);

        $this->propSetter($prop, $value);
    }

    /**
     * 预加载关联.
     *
     * @param array $relation
     *
     * @return \Leevel\Database\Ddd\Select
     */
    public static function eager(array $relation): Select
    {
        return (new static())->select()->eager($relation);
    }

    /**
     * 设置多对多中间实体.
     *
     * @param \Leevel\Database\Ddd\IEntity $middle
     */
    public function withMiddle(IEntity $middle): void
    {
        $this->leevelRelationMiddle = $middle;
    }

    /**
     * 获取多对多中间实体.
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function middle(): ?IEntity
    {
        return $this->leevelRelationMiddle;
    }

    /**
     * 一对一关联.
     *
     * @param string $relatedEntityClass
     * @param string $targetKey
     * @param string $sourceKey
     *
     * @return \Leevel\Database\Ddd\Relation\HasOne
     */
    public function hasOne(string $relatedEntityClass, string $targetKey, string $sourceKey): HasOne
    {
        $entity = new $relatedEntityClass();

        $this->validateRelationField($entity, $targetKey);
        $this->validateRelationField($this, $sourceKey);

        return new HasOne($entity, $this, $targetKey, $sourceKey);
    }

    /**
     * 定义从属关系.
     *
     * @param string $relatedEntityClass
     * @param string $targetKey
     * @param string $sourceKey
     *
     * @return \Leevel\Database\Ddd\Relation\BelongsTo
     */
    public function belongsTo(string $relatedEntityClass, string $targetKey, string $sourceKey): BelongsTo
    {
        $entity = new $relatedEntityClass();

        $this->validateRelationField($entity, $targetKey);
        $this->validateRelationField($this, $sourceKey);

        return new BelongsTo($entity, $this, $targetKey, $sourceKey);
    }

    /**
     * 一对多关联.
     *
     * @param string $relatedEntityClass
     * @param string $targetKey
     * @param string $sourceKey
     *
     * @return \Leevel\Database\Ddd\Relation\HasMany
     */
    public function hasMany(string $relatedEntityClass, string $targetKey, string $sourceKey): HasMany
    {
        $entity = new $relatedEntityClass();

        $this->validateRelationField($entity, $targetKey);
        $this->validateRelationField($this, $sourceKey);

        return new HasMany($entity, $this, $targetKey, $sourceKey);
    }

    /**
     * 多对多关联.
     *
     * @param string $relatedEntityClass
     * @param string $middleEntityClass
     * @param string $targetKey
     * @param string $sourceKey
     * @param string $middleTargetKey
     * @param string $middleSourceKey
     *
     * @return \Leevel\Database\Ddd\Relation\ManyMany
     */
    public function manyMany(string $relatedEntityClass, string $middleEntityClass, string $targetKey, string $sourceKey, string $middleTargetKey, string $middleSourceKey): ManyMany
    {
        $entity = new $relatedEntityClass();
        $middleEntity = new $middleEntityClass();

        $this->validateRelationField($entity, $targetKey);
        $this->validateRelationField($middleEntity, $middleTargetKey);
        $this->validateRelationField($this, $sourceKey);
        $this->validateRelationField($middleEntity, $middleSourceKey);

        return new ManyMany(
            $entity, $this, $middleEntity, $targetKey,
            $sourceKey, $middleTargetKey, $middleSourceKey
        );
    }

    /**
     * 返回模型实体事件处理器.
     *
     * @return \Leevel\Event\IDispatch
     */
    public static function eventDispatch(): ?IDispatch
    {
        return static::$leevelDispatch;
    }

    /**
     * 设置模型实体事件处理器.
     *
     * @param null|\Leevel\Event\IDispatch $dispatch
     */
    public static function withEventDispatch(IDispatch $dispatch = null): void
    {
        static::$leevelDispatch = $dispatch;
    }

    /**
     * 注册模型实体事件.
     *
     * @param string                                 $event
     * @param \Closure|\Leevel\Event\Observer|string $listener
     */
    public static function event(string $event, $listener): void
    {
        if (null === static::$leevelDispatch &&
            static::lazyloadPlaceholder() && null === static::$leevelDispatch) {
            return;
        }

        static::isSupportEvent($event);

        static::$leevelDispatch->register(
            "entity.{$event}:".static::class,
            $listener
        );
    }

    /**
     * 执行模型实体事件.
     *
     * @param string $event
     */
    public function handleEvent(string $event, ...$args): void
    {
        if (null === static::$leevelDispatch) {
            return;
        }

        $this->isSupportEvent($event);

        array_unshift($args, $this);
        array_unshift($args, "entity.{$event}:".get_class($this));

        static::$leevelDispatch->handle(...$args);
    }

    /**
     * 验证事件是否受支持
     *
     * @param string $event
     */
    public static function isSupportEvent(string $event): void
    {
        if (!in_array($event, static::supportEvent(), true)) {
            $e = sprintf('Event `%s` do not support.');

            throw new InvalidArgumentException($e);
        }
    }

    /**
     * 返回受支持的事件.
     *
     * @return array
     */
    public static function supportEvent(): array
    {
        return [
            static::BEFORE_SELECT_EVENT,
            static::AFTER_SELECT_EVENT,
            static::BEFORE_FIND_EVENT,
            static::AFTER_FIND_EVENT,
            static::BEFORE_SAVE_EVENT,
            static::AFTER_SAVE_EVENT,
            static::BEFORE_CREATE_EVENT,
            static::AFTER_CREATE_EVENT,
            static::BEFORE_UPDATE_EVENT,
            static::AFTER_UPDATE_EVENT,
            static::BEFORE_DELETE_EVENT,
            static::AFTER_DELETE_EVENT,
            static::BEFORE_SOFT_DELETE_EVENT,
            static::AFTER_SOFT_DELETE_EVENT,
            static::BEFORE_SOFT_RESTORE_EVENT,
            static::AFTER_SOFT_RESTORE_EVENT,
        ];
    }

    /**
     * 返回已经改变.
     *
     * @return array
     */
    public function changed(): array
    {
        return $this->leevelChangedProp;
    }

    /**
     * 检测是否已经改变.
     *
     * @param string $prop
     *
     * @return bool
     */
    public function hasChanged(string $prop): bool
    {
        return in_array($prop, $this->leevelChangedProp, true);
    }

    /**
     * 将指定的属性设置已改变.
     *
     * @param array $props
     *
     * @return $this
     */
    public function addChanged(array $props): IEntity
    {
        foreach ($props as $prop) {
            if (!in_array($prop, $this->leevelChangedProp, true)) {
                continue;
            }

            $this->leevelChangedProp[] = $prop;
        }

        return $this;
    }

    /**
     * 删除改变属性.
     *
     * @param array $props
     *
     * @return $this
     */
    public function deleteChanged(array $props): IEntity
    {
        $this->leevelChangedProp = array_values(array_diff($this->leevelChangedProp, $props));

        return $this;
    }

    /**
     * 清空改变属性.
     *
     * @return $this
     */
    public function clearChanged(): IEntity
    {
        $this->leevelChangedProp = [];

        return $this;
    }

    /**
     * 返回主键字段.
     *
     * @return null|array|string
     */
    public static function primaryKey()
    {
        $keys = static::primaryKeys();

        return 1 === count($keys) ? reset($keys) : $keys;
    }

    /**
     * 返回主键字段.
     *
     * @return array
     */
    public static function primaryKeys(): array
    {
        return (array) static::ID;
    }

    /**
     * 返回自动增长字段.
     *
     * @return string
     */
    public static function autoIncrement(): ?string
    {
        return static::AUTO;
    }

    /**
     * 返回字段名字.
     *
     * @return array
     */
    public static function fields(): array
    {
        return static::STRUCT;
    }

    /**
     * 是否存在字段.
     *
     * @param string $field
     *
     * @return bool
     */
    public static function hasField(string $field): bool
    {
        return array_key_exists($field, static::fields());
    }

    /**
     * 返回供查询的主键字段
     * 复合主键或者没有主键直接抛出异常.
     *
     * @return string
     */
    public static function singlePrimaryKey(): string
    {
        $key = static::primaryKey();

        if (!is_string($key)) {
            $e = sprintf('Entity %s do not have primary key or composite id not supported.', static::class);

            throw new InvalidArgumentException($e);
        }

        return $key;
    }

    /**
     * 返回供查询的主键字段值
     * 复合主键或者没有主键直接抛出异常.
     *
     * @return mixed
     */
    public function singleId()
    {
        $this->singlePrimaryKey();

        return $this->id();
    }

    /**
     * 返回设置表.
     *
     * @return string
     */
    public static function table(): string
    {
        return static::TABLE;
    }

    /**
     * 设置连接.
     *
     * @param mixed $connect
     *
     * @return $this
     */
    public function withConnect($connect): IEntity
    {
        $this->leevelConnect = $connect;

        return $this;
    }

    /**
     * 获取 enum.
     * 不存在返回 false.
     *
     * @param string $prop
     * @param mixed  $enum
     * @param string $separate
     *
     * @return mixed
     */
    public static function enum(string $prop, $enum = null, string $separate = ',')
    {
        $prop = static::normalize($prop);
        $enumDefined = static::class.'::'.strtoupper($prop).'_ENUM';

        if (!defined($enumDefined)) {
            return false;
        }

        if (!isset(static::$leevelEnums[static::class]) ||
            !isset(static::$leevelEnums[static::class][$prop])) {
            $enums = constant($enumDefined);
            $enums = array_values($enums);

            foreach ($enums as &$e) {
                if (!isset($e[1])) {
                    $e = sprintf('Invalid enum in the field `%s` of entity `%s`.', $prop, static::class);

                    throw new InvalidArgumentException($e);
                }

                $e[1] = __($e[1]);
            }

            static::$leevelEnums[static::class][$prop] = $enums;
        } else {
            $enums = static::$leevelEnums[static::class][$prop];
        }

        if (null === $enum) {
            return $enums;
        }

        $enums = array_column($enums, 1, 0);
        $enumSep = explode(',', (string) $enum);

        foreach ($enumSep as $v) {
            if (!isset($enums[$v]) && !isset($enums[(int) $v])) {
                $e = sprintf('Value not a enum in the field `%s` of entity `%s`.', $prop, static::class);

                throw new InvalidArgumentException($e);
            }

            $result[] = isset($enums[$v]) ? $enums[$v] : $enums[(int) $v];
        }

        return implode($separate, $result);
    }

    /**
     * 对象转数组.
     *
     * @return array
     */
    public function toArray(): array
    {
        return $this->toArraySource(...func_get_args());
    }

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

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

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

    /**
     * 创建一个模型实体集合.
     *
     * @param array $entity
     *
     * @return \Leevel\Collection\Collection
     */
    public function collection(array $entity = []): Collection
    {
        return new Collection($entity);
    }

    /**
     * 获取查询键值.
     *
     * @return array
     */
    public function idCondition(): array
    {
        if (null === (($ids = $this->id()))) {
            $e = sprintf('Entity %s has no primary key data.', static::class);

            throw new InvalidArgumentException($e);
        }

        if (!is_array($ids)) {
            $ids = [$this->singlePrimaryKey() => $ids];
        }

        return $ids;
    }

    /**
     * 设置作用域查询对象.
     *
     * @param \Leevel\Database\Select $select
     */
    public function withScopeSelect(DatabaseSelect $select): void
    {
        $this->leevelScopeSelect = $select;
    }

    /**
     * 返回数据库查询集合对象.
     *
     * @return \Leevel\Database\Select
     */
    public function databaseSelect(): DatabaseSelect
    {
        if ($this->leevelScopeSelect) {
            return $this->leevelScopeSelect;
        }

        return $this
            ->metaConnect()
            ->select()
            ->asClass(static::class, [true])
            ->asCollection();
    }

    /**
     * 返回数据库查询集合对象.
     *
     * @return \Leevel\Database\Ddd\Select
     */
    public function select(): Select
    {
        return new Select($this);
    }

    /**
     * 返回模型实体类的 meta 对象.
     *
     * @return \Leevel\Database\Ddd\IMeta
     */
    public function metaConnect(): IMeta
    {
        return static::meta($this->leevelConnect);
    }

    /**
     * 返回模型实体类的 meta 对象.
     *
     * @param mixed $connect
     *
     * @return \Leevel\Database\Ddd\IMeta
     */
    public static function meta($connect = null): IMeta
    {
        return Meta::instance(static::TABLE)->setConnect($connect);
    }

    /**
     * 实现 ArrayAccess::offsetExists.
     *
     * @param string $index
     *
     * @return bool
     */
    public function offsetExists($index): bool
    {
        return $this->hasProp($index);
    }

    /**
     * 实现 ArrayAccess::offsetSet.
     *
     * @param string $index
     * @param mixed  $newval
     */
    public function offsetSet($index, $newval): void
    {
        $this->withPropValue($index, $newval);
    }

    /**
     * 实现 ArrayAccess::offsetGet.
     *
     * @param string $index
     *
     * @return mixed
     */
    public function offsetGet($index)
    {
        return $this->propValue($index);
    }

    /**
     * 实现 ArrayAccess::offsetUnset.
     *
     * @param string $index
     */
    public function offsetUnset($index): void
    {
        $this->offsetSet($index, null);
    }

    /**
     * 保存统一入口.
     *
     * @param string     $method
     * @param array      $data
     * @param null|array $fill
     *
     * @return $this
     */
    protected function saveEntry(string $method, array $data, ?array $fill = null): IEntity
    {
        foreach ($data as $k => $v) {
            $this->withPropValue($k, $v);
        }

        $this->handleEvent(static::BEFORE_SAVE_EVENT);

        // 程序通过内置方法统一实现
        switch (strtolower($method)) {
            case 'create':
                $this->createReal($fill);

                break;
            case 'update':
                $this->updateReal($fill);

                break;
            case 'replace':
                $this->replaceReal($fill);

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

                if (is_array($ids)) {
                    $this->replaceReal($fill);
                } else {
                    if (empty($ids)) {
                        $this->createReal($fill);
                    } else {
                        $this->updateReal($fill);
                    }
                }

                break;
        }

        return $this;
    }

    /**
     * 添加数据.
     *
     * @param null|array $fill
     *
     * @return $this
     */
    protected function createReal(?array $fill = null): IEntity
    {
        $this->leevelFlushed = false;

        $this->parseAutoFill('create', $fill);

        $propKey = $this->normalizeWhiteAndBlack(
            array_flip($this->leevelChangedProp), 'create_prop'
        );

        $saveData = [];

        foreach ($this->leevelChangedProp as $prop) {
            if (!array_key_exists($prop, $propKey)) {
                continue;
            }

            $saveData[$prop] = $this->__get($prop);
        }

        if (!$saveData) {
            if (null === (($primaryKey = $this->primaryKeys()))) {
                $e = sprintf('Entity %s has no primary key.', static::class);

                throw new InvalidArgumentException($e);
            }

            foreach ($primaryKey as $value) {
                $saveData[$value] = null;
            }
        }

        $this->leevelFlush = function ($saveData) {
            $this->handleEvent(static::BEFORE_CREATE_EVENT, $saveData);

            $lastInsertId = $this->metaConnect()->insert($saveData);

            if ($auto = $this->autoIncrement()) {
                $this->withPropValue($auto, $lastInsertId, false, true);
            }

            $this->leevelNewed = false;

            $this->clearChanged();

            $this->handleEvent(static::AFTER_CREATE_EVENT, $saveData);

            return $lastInsertId;
        };

        $this->leevelFlushData = [$saveData];

        return $this;
    }

    /**
     * 更新数据.
     *
     * @param null|array $fill
     *
     * @return $this
     */
    protected function updateReal(?array $fill = null): IEntity
    {
        $this->leevelFlushed = false;

        $this->parseAutoFill('update', $fill);

        $propKey = $this->normalizeWhiteAndBlack(
            array_flip($this->leevelChangedProp), 'update_prop'
        );

        $saveData = [];

        foreach ($this->leevelChangedProp as $prop) {
            if (!array_key_exists($prop, $propKey)) {
                continue;
            }

            $saveData[$prop] = $this->__get($prop);
        }

        if (!$saveData) {
            return $this;
        }

        $condition = [];

        foreach ($this->primaryKeys() as $field) {
            if (isset($saveData[$field])) {
                unset($saveData[$field]);
            }

            if ($value = $this->__get($field)) {
                $condition[$field] = $value;
            }
        }

        if (empty($condition) || empty($saveData)) {
            return $this;
        }

        $this->leevelFlush = function ($condition, $saveData) {
            $this->handleEvent(static::BEFORE_UPDATE_EVENT, $saveData, $condition);

            $num = $this->metaConnect()->update($condition, $saveData);

            $this->handleEvent(static::BEFORE_UPDATE_EVENT, null, null);

            $this->clearChanged();

            $this->handleEvent(static::AFTER_UPDATE_EVENT);

            return $num;
        };

        $this->leevelFlushData = [$condition, $saveData];

        return $this;
    }

    /**
     * 模拟 replace 数据.
     *
     * @param null|array $fill
     */
    protected function replaceReal(?array $fill = null): void
    {
        $this->leevelReplace = $fill;
        $this->createReal($fill);
    }

    /**
     * 改变属性.
     *
     * @param string $prop
     * @param mixed  $value
     * @param bool   $force
     * @param bool   $ignoreReadonly
     */
    protected function withPropValue(string $prop, $value, bool $force = true, bool $ignoreReadonly = false): void
    {
        $prop = $this->normalize($prop);

        $this->validate($prop);

        if ($this->isRelation($prop)) {
            $e = sprintf('Cannot set a relation prop `%s` on entity `%s`.', $prop, static::class);

            throw new InvalidArgumentException($e);
        }

        $this->propSetter($prop, $value);

        if (!$force) {
            return;
        }

        if (false === $ignoreReadonly &&
            isset(static::STRUCT[$prop]['readonly']) &&
            true === static::STRUCT[$prop]['readonly']) {
            $e = sprintf('Cannot set a read-only prop `%s` on entity `%s`.', $prop, static::class);

            throw new InvalidArgumentException($e);
        }

        if (in_array($prop, $this->leevelChangedProp, true)) {
            return;
        }

        $this->leevelChangedProp[] = $prop;
    }

    /**
     * 返回属性.
     *
     * @param string $prop
     *
     * @return mixed
     */
    protected function propValue(string $prop)
    {
        $prop = $this->normalize($prop);

        $this->validate($prop);

        if (!$this->isRelation($prop)) {
            return $this->propGetter($prop);
        }

        return $this->loadRelationProp($prop);
    }

    /**
     * 是否存在属性.
     *
     * @param string $prop
     *
     * @return bool
     */
    protected function hasProp(string $prop): bool
    {
        $prop = $this->normalize($prop);

        if (!$this->hasField($prop)) {
            return false;
        }

        $prop = $this->asProp($prop);

        if (!property_exists($this, $prop)) {
            $e = sprintf('Prop `%s` of entity `%s` was not defined.', $prop, get_class($this));

            throw new InvalidArgumentException($e);
        }

        return true;
    }

    /**
     * 取得 getter 数据.
     *
     * @param string $prop
     *
     * @return mixed
     */
    protected function propGetter(string $prop)
    {
        return $this->{'getter'.ucfirst($this->asProp($prop))}();
    }

    /**
     * 设置 setter 数据.
     *
     * @param string $prop
     * @param mixed  $value
     */
    protected function propSetter(string $prop, $value): void
    {
        $this->{'setter'.ucfirst($this->asProp($prop))}($value);
    }

    /**
     * 自动填充.
     *
     * @param string $type
     * @param array  $fill
     */
    protected function parseAutoFill(string $type, ?array $fill = null): void
    {
        if (null === $fill) {
            return;
        }

        foreach (static::STRUCT as $prop => $value) {
            if ($fill && !in_array($prop, $fill, true)) {
                continue;
            }

            if (array_key_exists($type.'_fill', $value)) {
                $this->normalizeFill($prop, $value[$type.'_fill']);
            }
        }
    }

    /**
     * 格式化自动填充.
     *
     * @param string $prop
     * @param mixed  $value
     */
    protected function normalizeFill(string $prop, $value): void
    {
        if (null === $value) {
            $camelizeClass = 'fill'.ucfirst($this->asProp($prop));

            if (method_exists($this, $camelizeClass)) {
                $value = $this->{$camelizeClass}($this->propValue($prop));
            }
        }

        $this->withPropValue($prop, $value);
    }

    /**
     * 从关联中读取数据.
     *
     * @param string $prop
     *
     * @return mixed
     */
    protected function loadDataFromRelation(string $prop)
    {
        $relation = $this->loadRelation($prop);
        $result = $relation->sourceQuery();

        $this->withRelationProp($prop, $result);

        return $result;
    }

    /**
     * 校验并转换 prop.
     *
     * @param string $prop
     *
     * @return string
     */
    protected function prop(string $prop): string
    {
        $this->validate($prop);

        return $this->asProp($prop);
    }

    /**
     * 验证 getter setter 属性.
     *
     * @param string $prop
     */
    protected function validate(string $prop): void
    {
        $prop = $this->normalize($prop);

        if (!$this->hasProp($prop)) {
            $e = sprintf('Entity `%s` prop or field of struct `%s` was not defined.', get_class($this), $prop);

            throw new InvalidArgumentException($e);
        }
    }

    /**
     * 校验关联字段定义.
     *
     * @param array $defined
     * @param array $field
     */
    protected function validateRelationDefined(array $defined, array $field): void
    {
        foreach ($field as $v) {
            if (!isset($defined[$v])) {
                $e = sprintf('Relation `%s` field was not defined.', $v);

                throw new InvalidArgumentException($e);
            }
        }
    }

    /**
     * 验证关联字段.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     * @param string                       $field
     */
    protected function validateRelationField(IEntity $entity, string $field): void
    {
        if (!$entity->hasField($field)) {
            $e = sprintf(
                'The field `%s`.`%s` of entity `%s` was not defined.',
                $entity->table(), $field, get_class($entity)
            );

            throw new InvalidArgumentException($e);
        }
    }

    /**
     * 格式化黑白名单数据.
     *
     * @param array  $key
     * @param string $type
     *
     * @return array
     */
    protected function normalizeWhiteAndBlack(array $key, string $type): array
    {
        return $this->whiteAndBlack(
            $key,
            $this->leevelBlackWhites[$type]['white'],
            $this->leevelBlackWhites[$type]['black']
        );
    }

    /**
     * 对象转数组.
     *
     * @return array
     */
    protected function toArraySource(array $white = [], array $black = [], string $separate = ','): array
    {
        if ($white || $black) {
            $prop = $this->whiteAndBlack(
                $this->fields(), $white, $black
            );
        } else {
            $prop = $this->normalizeWhiteAndBlack(
                $this->fields(), 'show_prop'
            );
        }

        $result = [];

        foreach ($prop as $k => $value) {
            if ($this->isRelation($k)) {
                continue;
            }

            $value = $this->propValue($k);
            $result[$k] = $value;

            $result = static::prepareEnum($k, $result, $separate);
        }

        return $result;
    }

    /**
     * 准备 enum 数据.
     *
     * @param string $prop
     * @param array  $data
     * @param string $separate
     *
     * @return array
     */
    protected static function prepareEnum(string $prop, array $data, string $separate = ','): array
    {
        if (!isset($data[$prop])) {
            return $data;
        }

        if (false === ($enum = static::enum($prop, $data[$prop], $separate))) {
            return $data;
        }

        $data[$prop.'_'.self::ENUM] = $enum;

        return $data;
    }

    /**
     * 黑白名单数据解析.
     *
     * @param array $key
     * @param array $white
     * @param array $black
     *
     * @return array
     */
    protected function whiteAndBlack(array $key, array $white, array $black): array
    {
        if ($white) {
            $key = array_intersect_key($key, array_flip($white));
        } elseif ($black) {
            $key = array_diff_key($key, array_flip($black));
        }

        return $key;
    }

    /**
     * 延迟载入占位符.
     *
     * @return bool
     */
    protected static function lazyloadPlaceholder(): bool
    {
        return Lazyload::placeholder();
    }

    /**
     * 统一处理前转换下划线命名风格.
     *
     * @param string $name
     *
     * @return string
     */
    protected static function normalize(string $prop): string
    {
        if (isset(static::$leevelUnCamelize[$prop])) {
            return static::$leevelUnCamelize[$prop];
        }

        return static::$leevelUnCamelize[$prop] = un_camelize($prop);
    }

    /**
     * 返回转驼峰命名.
     *
     * @param string $prop
     *
     * @return string
     */
    protected function asProp(string $prop): string
    {
        if (isset(static::$leevelCamelize[$prop])) {
            return static::$leevelCamelize[$prop];
        }

        return static::$leevelCamelize[$prop] = camelize($prop);
    }
}

// import fn.
class_exists(un_camelize::class);
class_exists(camelize::class);
class_exists(gettext::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\Protocol;

use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Http\RedirectResponse;
use Leevel\Http\Request;
use Leevel\Kernel\IKernel;
use Swoole\Http\Request as SwooleHttpRequest;
use Swoole\Http\Response as SwooleHttpResponse;
use Swoole\Http\Server as SwooleHttpServer;

/**
 *  Http 服务
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.12.25
 *
 * @version 1.0
 */
class HttpServer extends Server implements IServer
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        // 监听 IP 地址
        // see https://wiki.swoole.com/wiki/page/p-server.html
        // see https://wiki.swoole.com/wiki/page/327.html
        'host' => '0.0.0.0',

        // 监听端口
        // see https://wiki.swoole.com/wiki/page/p-server.html
        // see https://wiki.swoole.com/wiki/page/327.html
        'port' => '9501',

        // swoole 进程名称
        'process_name' => 'leevel.http',

        // swoole 进程保存路径
        'pid_path' => '',

        // 设置启动的 worker 进程数
        // see https://wiki.swoole.com/wiki/page/275.html
        'worker_num' => 8,

        // 守护进程化
        // see https://wiki.swoole.com/wiki/page/278.html
        'daemonize' => 0,

        // 开启静态路径
        // 配合 Nginx 可以设置这里为 false,nginx 设置规则解析静态路径动态路由转发给 swoole
        'enable_static_handler' => false,

        // 开启静态路径目录
        'document_root' => '',

        // 自定义进程
        'processes' => [],
    ];

    /**
     * 服务回调事件.
     *
     * @var array
     */
    protected $serverEvent = [
        'start',
        'connect',
        'workerStart',
        'managerStart',
        'workerStop',
        'request',
        'shutdown',
        'close',
    ];

    /**
     * 处理 http 请求
     * 浏览器连接服务器后, 页面上的每个请求均会执行一次
     * nginx 反向代理每次打开链接页面默认都是接收两个请求, 一个是正常的数据请求, 一个 favicon.ico 的请求
     * 可以通过 nginx deny 屏蔽掉 favicon.ico 的请求，具体请 Google 或者百度.
     *
     * @param \Swoole\Http\Request  $swooleRequest
     * @param \Swoole\Http\Response $swooleResponse
     */
    public function onRequest(SwooleHttpRequest $swooleRequest, SwooleHttpResponse $swooleResponse): void
    {
        // 请求过滤 favicon
        if ('/favicon.ico' === $swooleRequest->server['path_info'] ||
            '/favicon.ico' === $swooleRequest->server['request_uri']) {
            $swooleResponse->end(' ');

            return;
        }

        $request = $this->normalizeRequest($swooleRequest);
        $response = $this->dispatchRouter($request);

        $swooleResponse = $this->normalizeResponse($response, $swooleResponse);
        $swooleResponse->end();
    }

    /**
     * 监听连接关闭事件
     * 每个浏览器连接关闭时执行一次, reload 时连接不会断开, 也就不会触发该事件.
     *
     * @param \Swoole\Http\Server $server
     * @param int                 $fd
     * @param int                 $reactorId
     *
     * @see https://wiki.swoole.com/wiki/page/p-event/onClose.html
     */
    public function onHttpClose(SwooleHttpServer $server, int $fd, int $reactorId): void
    {
        $this->log(
            sprintf(
                'Server close, fd %d, reactorId %d.',
                $fd, $reactorId
            )
        );
    }

    /**
     * 路由调度.
     *
     * @param \Leevel\Http\IRequest $request
     *
     * @return \Leevel\Http\IResource
     */
    protected function dispatchRouter(IRequest $request): IResponse
    {
        $kernel = $this->container->make(IKernel::class);

        $response = $kernel->handle($request);

        $kernel->terminate($request, $response);

        $this->removeCoroutine();

        return $response;
    }

    /**
     * 格式化 QueryPHP 响应到 swoole 响应.
     *
     * @param \Leevel\Http\IResponse $response
     * @param \Swoole\Http\Response  $swooleResponse
     *
     * @return \Swoole\Http\Response
     */
    protected function normalizeResponse(IResponse $response, SwooleHttpResponse $swooleResponse): SwooleHttpResponse
    {
        foreach ($response->getCookies() as $item) {
            call_user_func_array([$swooleResponse, 'cookie'], $item);
        }

        if ($response instanceof RedirectResponse &&
            method_exists($swooleResponse, 'redirect')) {
            $swooleResponse->redirect($response->getTargetUrl());
        }

        foreach ($response->headers->all() as $key => $value) {
            $swooleResponse->header($key, $value);
        }

        $swooleResponse->status($response->getStatusCode());

        $swooleResponse->write($response->getContent() ?: ' ');

        return $swooleResponse;
    }

    /**
     * 格式化 swoole 请求到 QueryPHP 请求
     *
     * @param \Swoole\Http\Request $swooleRequest
     *
     * @return \Leevel\Http\IRequest
     */
    protected function normalizeRequest(SwooleHttpRequest $swooleRequest): IRequest
    {
        $request = new Request();

        $datas = [
            'header' => 'headers',
            'server' => 'server',
            'cookie' => 'cookies',
            'get'    => 'query',
            'files'  => 'files',
            'post'   => 'request',
        ];

        $servers = [];

        if ($swooleRequest->header) {
            $tmp = $tmpHeader = [];

            foreach ($swooleRequest->header as $key => $value) {
                $key = strtoupper(str_replace('-', '_', $key));
                $tmpHeader[$key] = $value;

                $key = 'HTTP_'.$key;
                $tmp[$key] = $value;
            }

            $servers = $tmp;
            $swooleRequest->header = $tmpHeader;
        }

        if ($swooleRequest->server) {
            $swooleRequest->server = array_change_key_case(
                $swooleRequest->server,
                CASE_UPPER
            );

            $servers = array_merge($servers, $swooleRequest->server);
            $swooleRequest->server = $servers;
        } else {
            $swooleRequest->server = $servers ?: null;
        }

        foreach ($datas as $key => $item) {
            if ($swooleRequest->{$key}) {
                $request->{$item}->replace($swooleRequest->{$key});
            }
        }

        return $request;
    }

    /**
     * 创建 http server.
     */
    protected function createServer(): void
    {
        unset($this->option['task_worker_num']);

        $this->server = new SwooleHttpServer(
            $this->option['host'],
            (int) ($this->option['port'])
        );

        $this->initServer();

        // 删除容器中注册为 -1 的数据
        $this->removeCoroutine();
    }
}


<?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\Console;

use function Leevel\Support\Str\ends_with;
use Leevel\Support\Str\ends_with;
use RuntimeException;

/**
 * 命令行工具类导入类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.11.06
 *
 * @version 1.0
 */
class Load
{
    /**
     * 载入命名空间.
     *
     * @var array
     */
    protected $namespaces = [];

    /**
     * 已经载入数据.
     *
     * @var array
     */
    protected $loaded;

    /**
     * 构造函数.
     *
     * @param array $namespaces
     */
    public function __construct(array $namespaces = [])
    {
        $this->namespaces = $namespaces;
    }

    /**
     * 添加命名空间.
     *
     * @param array $namespaces
     *
     * @return $this
     */
    public function addNamespace(array $namespaces): self
    {
        $this->namespaces = array_merge($this->namespaces, $namespaces);

        return $this;
    }

    /**
     * 载入命令行数据.
     *
     * @return array
     */
    public function loadData(): array
    {
        if (null !== $this->loaded) {
            return $this->loaded;
        }

        $files = $this->findConsoleFile($this->namespaces);

        return $this->loaded = $files;
    }

    /**
     * 分析目录中的 PHP 命令包包含的文件.
     *
     * @param array $namespaces
     *
     * @return array
     */
    public function findConsoleFile(array $namespaces): array
    {
        $files = [];

        foreach ($namespaces as $key => $dir) {
            if (!is_dir($dir)) {
                throw new RuntimeException(sprintf('Console load dir %s is not exits.', $dir));
            }

            $currentFiles = glob($dir.'/*.php');
            $currentFiles = array_map(function ($item) use ($key) {
                return $key.'\\'.basename($item, '.php');
            }, $currentFiles);

            // 忽略索引
            $currentFiles = array_filter($currentFiles, function ($item) {
                return !ends_with($item, '\\index');
            });

            $files = array_merge($files, $currentFiles);
        }

        return $files;
    }
}

// import fn.
class_exists(ends_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\Mail;

use Swift_Events_EventListener;
use Swift_Mailer;
use Swift_Mime_SimpleMessage;
use Swift_Transport;

/**
 * connect 驱动抽象类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.26
 *
 * @version 1.0
 */
abstract class Connect implements Swift_Transport
{
    /**
     * swift mailer.
     *
     * @var \Swift_Mailer
     */
    protected $swiftMailer;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [];

    /**
     * 构造函数.
     *
     * @param array $option
     */
    public function __construct(array $option = [])
    {
        $this->option = array_merge($this->option, $option);

        $this->swiftMailer();
    }

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

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function isStarted()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function start()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function stop()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function ping()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
    }

    /**
     * {@inheritdoc}
     */
    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
    {
        return $this->getSwiftMailer()->send($message, $failedRecipients);
    }

    /**
     * 返回 swift mailer.
     *
     * @return \Swift_Mailer
     */
    public function getSwiftMailer(): Swift_Mailer
    {
        return $this->swiftMailer;
    }

    /**
     * 生成 swift mailer.
     *
     * @return \Swift_Mailer
     */
    protected function swiftMailer(): Swift_Mailer
    {
        return $this->swiftMailer = new Swift_Mailer(
            $this->makeTransport()
        );
    }
}


<?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 Leevel\Filesystem\Fso\create_directory;
use function Leevel\Filesystem\Fso\create_directory;
use Leevel\Protocol\Process as ProtocolProcess;
use Swoole\Process;
use Swoole\Server as SwooleServer;

/**
 * swoole 服务基类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.12.25
 * @see https://www.swoole.com/
 * @see https://www.cnblogs.com/luojianqun/p/5355439.html
 *
 * @version 1.0
 */
abstract class Server
{
    /**
     * IOC 容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * swoole 服务实例.
     *
     * @var \Swoole\Server
     */
    protected $server;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [];

    /**
     * 服务回调事件.
     *
     * @var array
     */
    protected $serverEvent = [
        'start',
        'connect',
        'workerStart',
        'managerStart',
        'workerStop',
        'receive',
        'task',
        'finish',
        'shutdown',
        'close',
    ];

    /**
     * 构造函数.
     *
     * @param \Leevel\Di\IContainer $container
     * @param array                 $option
     */
    public function __construct(IContainer $container, array $option = [])
    {
        $this->validSwoole();

        $this->container = $container;

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

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

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

        return $this;
    }

    /**
     * 获取配置.
     *
     * @return array
     */
    public function getOption(): array
    {
        return $this->option;
    }

    /**
     * 添加自定义进程.
     *
     * @param string $process
     */
    public function process(string $process): void
    {
        $newProgress = new Process(
            function (Process $worker) use ($process) {
                $newProgress = $this->container->make($process);

                if (!is_object($newProgress) || ($newProgress instanceof ProtocolProcess)) {
                    throw new InvalidArgumentException(
                        sprintf('Process `%s` was invalid.', $process)
                    );
                }

                if (!is_callable([$newProgress, 'handle'])) {
                    throw new InvalidArgumentException(
                        sprintf('The `handle` of process `%s` was not found.', $process)
                    );
                }

                $worker->name($this->option['process_name'].'.'.$newProgress->getName());

                $newProgress->handle($this, $worker);
            }
        );

        $this->server->addProcess($newProgress);
    }

    /**
     * 运行服务.
     */
    public function startServer(): void
    {
        $this->checkPidPath();
        $this->createServer();
        $this->eventServer();
        $this->startSwooleServer();
    }

    /**
     * 返回服务.
     *
     * @return \Swoole\Server
     */
    public function getServer(): SwooleServer
    {
        return $this->server;
    }

    /**
     * 主进程的主线程
     * 记录进程 id,脚本实现自动重启.
     *
     * @param \Swoole\Server $server
     *
     * @see https://wiki.swoole.com/wiki/page/p-event/onStart.html
     */
    public function onStart(SwooleServer $server): void
    {
        $this->log(
            sprintf(
                'Server is started at %s:%d',
                $this->option['host'], $this->option['port']
            ),
            true, ''
        );

        $this->log('Server master worker start', true);

        $this->setProcessName($this->option['process_name'].'.master');

        $pid = $server->master_pid."\n".$server->manager_pid;

        $dirname = dirname($this->option['pid_path']);

        if (!is_writable($dirname) ||
            !file_put_contents($this->option['pid_path'], $pid)) {
            throw new InvalidArgumentException(
                sprintf('Dir %s is not writeable.', $dirname)
            );
        }

        chmod($this->option['pid_path'], 0666 & ~umask());

        // 开启验证模式
        //Runtime::enableStrictMode();
    }

    /**
     * 新的连接进入时
     * 每次连接时(相当于每个浏览器第一次打开页面时)执行一次, reload 时连接不会断开, 也就不会再次触发该事件.
     *
     * @param \Swoole\Server $server
     * @param int            $fd
     * @param int            $reactorId
     *
     * @see https://wiki.swoole.com/wiki/page/49.html
     */
    public function onConnect(SwooleServer $server, int $fd, int $reactorId): void
    {
        $this->log(
            sprintf(
                'Server connect, fd %d, reactorId %d.',
                $fd, $reactorId
            )
        );
    }

    /**
     * worker start 加载业务脚本常驻内存
     * 由于服务端命令行也采用 QueryPHP,无需再次引入 QueryPHP
     * 每个 Worker 进程启动或重启时都会执行.
     *
     * @param \Swoole\Server $server
     * @param int            $workeId
     *
     * @see https://wiki.swoole.com/wiki/page/p-event/onWorkerStart.html
     */
    public function onWorkerStart(SwooleServer $server, int $workeId): void
    {
        if ($workeId >= $this->option['worker_num']) {
            $this->setProcessName(
                $this->option['process_name'].'.task'
            );
        } else {
            $this->setProcessName(
                $this->option['process_name'].'.worker'
            );
        }

        // 开启 opcache 重连后需要刷新
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }

        if (function_exists('apc_clear_cache')) {
            apc_clear_cache();
        }
    }

    /**
     * 当管理进程启动时调用
     * 服务器启动时执行一次
     *
     * @param \Swoole\Server $server
     *
     * @see https://wiki.swoole.com/wiki/page/190.html
     */
    public function onManagerStart(SwooleServer $server): void
    {
        $this->log('Server manager worker start', true);

        $this->setProcessName(
            $this->option['process_name'].'.manager'
        );
    }

    /**
     * worker进程终止时发生
     *
     * @param \Swoole\Server $server
     * @param int            $workerId
     */
    public function onWorkerStop(SwooleServer $server, int $workerId): void
    {
        $this->log(
            sprintf(
                'Server %s worker %d shutdown',
                $server->setting['process_name'], $workerId
            )
        );
    }

    /**
     * 监听数据发送事件.
     *
     * @param \Swoole\Server $server
     * @param int            $fd
     * @param int            $reactorId
     * @param string         $data
     *
     * @see https://wiki.swoole.com/wiki/page/50.html
     */
    public function onReceive(SwooleServer $server, int $fd, int $reactorId, string $data): void
    {
    }

    /**
     * 监听连接 Finish 事件.
     *
     * @param \Swoole\Server $server
     * @param int            $taskId
     * @param string         $data
     *
     * @see https://wiki.swoole.com/wiki/page/136.html
     */
    public function onFinish(SwooleServer $server, int $taskId, string $data): void
    {
        $this->log(
            sprintf('Task %d finish, the result is %s', $taskId, $data)
        );
    }

    /**
     * 监听连接 task 事件.
     *
     * @param \Swoole\Server $server
     * @param int            $taskId
     * @param int            $fromId
     * @param string         $data
     *
     * @see https://wiki.swoole.com/wiki/page/134.html
     */
    public function onTask(SwooleServer $server, int $taskId, int $fromId, string $data): void
    {
        $this->log(
            sprintf(
                'Task %d form workder %d, the result is %s',
                $taskId, $fromId, $data
            )
        );

        $server->finish($data);
    }

    /**
     * Server 正常结束时发生
     * 服务器关闭时执行一次
     *
     * @param \Swoole\Server $server
     *
     * @see https://wiki.swoole.com/wiki/page/p-event/onShutdown.html
     */
    public function onShutdown(SwooleServer $server): void
    {
        if (is_file($this->option['pid_path'])) {
            unlink($this->option['pid_path']);
        }

        $this->log('Server shutdown');
    }

    /**
     * 清理协程上下文数据.
     */
    protected function removeCoroutine(): void
    {
        $this->container->removeCoroutine();
    }

    /**
     * 验证 pid_path 是否可用.
     */
    protected function checkPidPath(): void
    {
        if (!$this->option['pid_path']) {
            throw new InvalidArgumentException('Pid path is not set');
        }

        create_directory(dirname($this->option['pid_path']));
    }

    /**
     * 创建 server.
     */
    protected function createServer(): void
    {
        $this->server = new SwooleServer(
            $this->option['host'],
            (int) ($this->option['port'])
        );

        $this->initServer();
    }

    /**
     * 初始化 http server.
     */
    protected function initServer(): void
    {
        $this->server->set($this->option);

        foreach ($this->option['processes'] as $process) {
            $this->process($process);
        }
    }

    /**
     * http server 绑定事件.
     */
    protected function eventServer(): void
    {
        $type = get_class($this);
        $type = substr($type, strrpos($type, '\\') + 1);
        $type = str_replace('Server', '', $type);

        foreach ($this->serverEvent as $event) {
            if (!method_exists($this, $onEvent = 'on'.$type.ucfirst($event))) {
                $onEvent = 'on'.ucfirst($event);
            }

            $this->server->on($event, [$this, $onEvent]);
        }
    }

    /**
     * http server 启动.
     */
    protected function startSwooleServer(): void
    {
        $this->server->start();
    }

    /**
     * 设置 swoole 进程名称.
     *
     * @param string $name
     *
     * @see http://php.net/manual/zh/function.cli-set-process-title.php
     * @see https://wiki.swoole.com/wiki/page/125.html
     */
    protected function setProcessName(string $name): void
    {
        if (function_exists('cli_set_process_title')) {
            cli_set_process_title($name);
        } else {
            if (function_exists('swoole_set_process_name')) {
                swoole_set_process_name($name);
            } else {
                throw new InvalidArgumentException(
                    'Require cli_set_process_title or swoole_set_process_name.'
                );
            }
        }
    }

    /**
     * 是否为守候进程运行.
     *
     * @return bool
     */
    protected function daemonize(): bool
    {
        return !$this->option['daemonize'];
    }

    /**
     * 记录日志.
     *
     * @param string $message
     * @param bool   $force
     * @param string $formatTime
     */
    protected function log(string $message, bool $force = false, string $formatTime = 'H:i:s'): void
    {
        if (!$force && !$this->daemonize()) {
            return;
        }

        fwrite(STDOUT, $this->messageTime($message, $formatTime).PHP_EOL);
    }

    /**
     * 消息时间.
     *
     * @param string $message
     * @param string $formatTime
     *
     * @return string
     */
    protected function messageTime(string $message, string $formatTime = ''): string
    {
        return ($formatTime ? sprintf('[%s]', date($formatTime)) : '').$message;
    }

    /**
     * 验证 swoole 版本.
     */
    protected function validSwoole(): void
    {
        if (!extension_loaded('swoole')) {
            throw new InvalidArgumentException('Swoole was not installed.');
        }

        if (version_compare(phpversion('swoole'), '4.2.9', '<')) {
            throw new InvalidArgumentException('Swoole 4.2.9 OR Higher');
        }
    }
}

// import fn.
class_exists(create_directory::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\Validate;

use BadMethodCallException;
use Closure;
use InvalidArgumentException;
use Leevel\Support\FunctionNotFoundException;
use function Leevel\Support\Str\un_camelize;
use Leevel\Support\Str\un_camelize;
use PHPUnit\Framework\TestCase;
use Traversable;

/**
 * 断言.
 *
 * 提供一套精简版本的断言方便业务中很愉快地使用。
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2019.04.27
 *
 * @version 1.0
 *
 * @see https://github.com/beberlei/assert 参考这里，断言和验证器复用一致的规则
 */
class Assert
{
    /**
     * 校验值
     *
     * @var mixed
     */
    protected $value;

    /**
     * 默认消息.
     *
     * @var string
     */
    protected $message;

    /**
     * 是否延后提示错误.
     *
     * @var bool
     */
    protected $lazy;

    /**
     * 是否验证所有.
     *
     * @var bool
     */
    protected $all;

    /**
     * 验证错误消息.
     *
     * @var array
     */
    protected $error = [];

    /**
     * PHPUnit.
     *
     * @var \PHPUnit\Framework\TestCase
     */
    protected static $phpUnit;

    /**
     * 构造函数.
     *
     * @param mixed       $value
     * @param null|string $message
     * @param bool        $lazy
     * @param bool        $all
     */
    public function __construct($value, ?string $message = null, bool $lazy = false, bool $all = true)
    {
        $this->value = $value;
        $this->message = $message;
        $this->lazy = $lazy;
        $this->all = $all;
    }

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return bool|mixed
     */
    public function __call(string $method, array $args)
    {
        if (false === $this->all && $this->error) {
            return $this;
        }

        array_unshift($args, $this->value);

        if (false === self::validateAssert($method, $args)) {
            $message = self::normalizeMessage($args, $this->message);

            if (false === $this->lazy) {
                throw new AssertException($message);
            }

            $this->error[] = $message;
        }

        return $this;
    }

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return bool|mixed
     */
    public static function __callStatic(string $method, array $args)
    {
        if (false === self::validateAssert($method, $args)) {
            $message = self::normalizeMessage($args);

            throw new AssertException($message);
        }
    }

    /**
     * 创建断言对象已支持链式表达式.
     *
     * @example
     * Assert::make($value)
     *     ->notEmpty()
     *     ->lessThan([7]);
     *
     * @param mixed       $value
     * @param null|string $message
     *
     * @return static
     */
    public static function make($value, ?string $message = null): self
    {
        return new static($value, $message);
    }

    /**
     * 创建断言对象延迟抛出错误.
     *
     * @example
     * Assert::lazy($value)
     *     ->notEmpty()
     *     ->lessThan([7])
     *     ->flush();
     *
     * @param mixed       $value
     * @param null|string $message
     * @param bool        $all
     *
     * @return static
     */
    public static function lazy($value, ?string $message = null, bool $all = true): self
    {
        return new static($value, $message, true, $all);
    }

    /**
     * 释放并抛出验证
     *
     * @param null|\Closure $format
     *
     * @return bool
     */
    public function flush(Closure $format = null): bool
    {
        if ($this->error) {
            if (!$format) {
                $format = 'json_encode';
            }

            $e = $format($this->error);

            throw new AssertException($e);
        }

        return true;
    }

    /**
     * 设置 PHPUnit.
     *
     * @param \PHPUnit\Framework\TestCase $phpUnit
     */
    public static function setPhpUnit(TestCase $phpUnit = null): void
    {
        self::$phpUnit = $phpUnit;
    }

    /**
     * 校验断言
     *
     * @param string $method
     * @param array  $args
     *
     * @return bool
     */
    protected static function validateAssert(string $method, array $args): bool
    {
        if (!array_key_exists(0, $args)) {
            throw new InvalidArgumentException('Missing the first argument.');
        }

        // 匹配可选
        if (true === $result = self::matchOptional($method, $args)) {
            return true;
        }

        list($method, $optional) = $result;

        // 匹配多个值，可支持 optionalMulti
        if (true === $result = self::matchMulti($method, $args, $optional)) {
            return true;
        }

        list($method, $multi) = $result;

        // 验证
        if (false === self::validateRule($method, $multi)) {
            return false;
        }

        return true;
    }

    /**
     * 参数校验和消息整理.
     *
     * @param array       $args
     * @param null|string $message
     *
     * @return string
     */
    protected static function normalizeMessage(array $args, ?string $message = null): string
    {
        if (count($args) >= 2 && is_string($args[array_key_last($args)])) {
            $message = array_pop($args);
        } else {
            $message = $message ?? 'No exception messsage specified.';
        }

        return $message;
    }

    /**
     * 匹配可选规则.
     *
     * @param string $method
     * @param array  $args
     *
     * @return array|bool
     */
    protected static function matchOptional(string $method, array $args)
    {
        if (0 !== strpos($method, 'optional')) {
            return [$method, false];
        }

        if (null === $args[0]) {
            self::countPhpUnit();

            return true;
        }

        $method = substr($method, 8);

        return [$method, true];
    }

    /**
     * 匹配多个值
     *
     * @param string $method
     * @param array  $args
     * @param bool   $optional
     *
     * @return array|bool
     */
    protected static function matchMulti(string $method, array $args, bool $optional)
    {
        if (0 !== stripos($method, 'multi')) {
            return [$method, [$args]];
        }

        if (!is_array($args[0]) && !$args instanceof Traversable) {
            $e = sprintf('Invalid first argument for multi assert.');

            throw new InvalidArgumentException($e);
        }

        $multi = [];
        $argsSource = $args;

        foreach ($args[0] as $v) {
            if (null === $v && true === $optional) {
                continue;
            }

            $argsSource[0] = $v;
            $multi[] = $argsSource;
        }

        if (!$multi) {
            self::countPhpUnit();

            return true;
        }

        $method = substr($method, 5);

        return [$method, $multi];
    }

    /**
     * 校验规则.
     *
     * @param string $method
     * @param array  $multi
     *
     * @return bool
     */
    protected static function validateRule(string $method, array $multi): bool
    {
        try {
            $fn = __NAMESPACE__.'\\Helper\\validate_'.un_camelize($method);

            foreach ($multi as $m) {
                if (!function_exists($fn)) {
                    class_exists($fn);
                }

                if (false === $fn(...$m)) {
                    return false;
                }

                self::countPhpUnit();
            }
        } catch (FunctionNotFoundException $th) {
            $e = sprintf('Method `%s` is not exits.', $method);

            throw new BadMethodCallException($e);
        }

        return true;
    }

    /**
     * 记录 PHPUnit 断言数量.
     */
    protected static function countPhpUnit(): void
    {
        if (self::$phpUnit) {
            self::$phpUnit->assertSame(1, 1);
        }
    }
}

// import fn.
class_exists(un_camelize::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\Database\Provider;

use Leevel\Database\Ddd\Entity;
use Leevel\Database\Ddd\Meta;
use Leevel\Database\Ddd\UnitOfWork;
use Leevel\Database\Manager;
use Leevel\Di\IContainer;
use Leevel\Di\Provider;
use Leevel\Event\IDispatch;

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

    /**
     * bootstrap.
     *
     * @param \Leevel\Event\IDispatch $event
     */
    public function bootstrap(IDispatch $event): void
    {
        $this->eventDispatch($event);

        $this->meta();
    }

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'databases' => [
                'Leevel\\Database\\Manager',
            ],
            'database' => [
                'Leevel\\Database\\Database',
                'Leevel\\Database\\IDatabase',
            ],
            'work' => [
                'Leevel\\Database\\Ddd\UnitOfWork',
                'Leevel\\Database\\Ddd\IUnitOfWork',
            ],
            'database.lazyload',
        ];
    }

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

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

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

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

    /**
     * 注册 database.lazyload 服务.
     * 仅仅用于占位，必要时用于唤醒数据库服务提供者.
     */
    protected function databaseLazyload(): void
    {
        $this->container->singleton('database.lazyload');
    }

    /**
     * 设置模型实体事件.
     *
     * @param \Leevel\Event\IDispatch $event
     */
    protected function eventDispatch(IDispatch $event): void
    {
        Entity::withEventDispatch($event);
    }

    /**
     * Meta 设置数据库管理.
     */
    protected function meta(): void
    {
        Meta::setDatabaseResolver(function () {
            return $this->container['databases'];
        });
    }
}


<?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\Bootstrap;

use Exception;
use Leevel\I18n\I18n;
use Leevel\I18n\Load;
use Leevel\Kernel\IApp;

/**
 * 读取语言包.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.05.03
 *
 * @version 1.0
 */
class LoadI18n
{
    /**
     * 响应.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function handle(IApp $app): void
    {
        $i18nDefault = $app['option']['i18n\\default'];

        if ($app->isCachedI18n($i18nDefault)) {
            $data = (array) include $app->i18nCachedPath($i18nDefault);
        } else {
            $load = (new Load([$app->i18nPath()]))->

            setI18n($i18nDefault)->

            addDir($this->getExtend($app));

            $data = $load->loadData();
        }

        $app->instance('i18n', $i18n = new I18n($i18nDefault));

        $i18n->addtext($i18nDefault, $data);
    }

    /**
     * 获取扩展语言包.
     *
     * @param \Leevel\Kernel\IApp $app
     *
     * @return array
     */
    public function getExtend(IApp $app): array
    {
        $extend = $app['option']->get('_composer.i18ns', []);

        $path = $app->path();

        $extend = array_map(function (string $item) use ($path) {
            if (!is_file($item)) {
                $item = $path.'/'.$item;
            }

            if (!is_dir($item)) {
                throw new Exception(sprintf('I18n dir %s is not exist.', $item));
            }

            return $item;
        }, $extend);

        return $extend;
    }
}