bardoqi/sight

Sight 是一个简单的 Laravel 展示层,用于控制器后端,以加快您的编码速度。

0.1.4 2020-10-17 14:06 UTC

README

您的 Laravel 缺失的展示层

Build Status StyleCI Codecov branch Latest Stable Version Total Downloads License

关于 Sight

中文(Chinese)

在 Laravel 框架之前没有 MVP (模型视图展示) 模式。

也许你知道在服务器端没有展示层。但我应该告诉你,Sight 就是。

Sight 不仅可以将 MVC 网络应用程序转换为 MVP,而且也是一个很好的 API 应用程序转换库。

现在您可以使用 Sight,它具有优雅的架构,并为您提供优雅的 MVP 模式。

MVC 和 MVP 之间的区别在于,视图完全被动且不了解模型层。而在 MVC 中,它不是被动的且了解模型层。在合适的 MVP 中,如果存在视图类,它也不应该实现构造函数。

一个典型的 MVP 示例将包括以下部分

  • 数据访问层(数据映射器、ORM 等)
  • 业务逻辑(如验证和计算)
  • 被动的视图类(它可以是模板,但最好使用类)
  • 将模型和视图连接起来的展示者

Sight 用于连接业务逻辑层和视图或控制器(对于 API 应用程序)层。

Sight 是一个映射类,帮助您将原始数据从数据库查询转换为视图或客户端可以使用的数据。

因此 Sight 是 Laravel 框架的展示层。

我们相信,您在使用 Sight 编码时会更加快乐。

使用 Sight,您不再需要编写重复的不愉快代码。

使用 Sight,您不再需要编写搜索数组代码,因为我们通过映射转换连接数组。

使用 sight,您不需要为了一个字段而连接许多表。

使用 sight,您可以停止程序员在代码段 "for" 或 "foreach" 中查询表。

使用 sight,可以避免许多不必要的连接查询。

现在就享受 sight,请!

Sight 做什么?

从数据层到视图或 API 层,您需要编写许多 foreach 代码进行数据转换。

现在您不需要再编写 foreach 代码了。只需使用 "ToArray()"!

也许问题在于您将无法在未来使用 "foreach"! :)

为什么选择 Sight?

也许您的团队成员经常在 "foreach" 块中使用数据库查询。当您开始使用 Sight 时,不良代码将消失。

我们应尽量避免过多的连接查询。如果连接仅用于一个字段,我们应使用多次查询完成,但那时我们必须编写更多的代码!现在很简单。

也许您经常使用数据库视图查询多个 id 字段以转换为名称。这只是一个针对数据库性能和代码可读性的不良查询。使用 Sight,您可以禁止他们使用这些查询。

使用数据库,您可以进行 hasOne 和 hasMany 连接查询。但您不能通过 hasMany 连接查询获取一条记录。您必须手动合并记录,这需要多次查询。使用 Sight 将变得简单!

也许您使用过像 League\Fractal 这样的库,我们推荐您尝试使用 Sight!

也许您使用过类似 mutators 的功能。但您需要在执行一些操作后转换字段。使用 Sight,您可以在返回控制器之前完成它。

特性

  • 字段转换。例如:int 转换为 date、time、datetime 等
  • 外键ID转换。例如:id转换为name或其他字段通过数组连接。
  • 单数组转换和多个数组通过数组连接进行转换。
  • 一对一连接:例如,文章表中的image_id,以及images表中的id和image_url。您可以简单地获取image_url。
  • 多对一连接:例如,文章表中的image_ids如'3,5,7,9',以及images表中的id和image_url。您可以简单地获取image_url。
  • 简单的pluck函数可以获取外键数组。
  • 使用selectFields方法简单地隐藏您不想显示的字段。
  • 字段转换可以使用数据转换、数组路径和方法名。
  • 支持从数据库(如MySQL和ElasticSearch)查询数据并进行数组路径转换。
  • 支持从MySQL和ElasticSearch中扁平化JSON类型数据。
  • 关于数组连接,支持内连接和外连接。
  • 高性能,因为每个数组只需要遍历两次。

入门指南

安装

$ composer require bardoqi/sight

代码示例

非常简单!您就像使用Model一样使用它。

函数链就像

    
    $this->selectFields()
        ->fromLocal()
        ->innerJoinForeign()
        ->onRelation()
        ->setMapping()
        ->toArray();

就像在Model或SQL中

    $this->select()
        ->from()
        ->join()
        ->on()
        ->toArray();
        
        

当然,它与Model完全不同。

首先,您应该创建一个扩展Presenter的presenter类。例如;

namespace App\Presenter

use Bardoqi\Sight\Presenter;

class ArticlePresenter extents Presenter
{
   public function __construct(){
       parent::__construct();
   }  
}

然后您可以添加一个获取数据的功能。

namespace App\Presenter

use Bardoqi\Sight\Presenter;
use Bardoqi\Sight\Traits\PresenterTrait;
use Bardoqi\Sight\Enums\MappingTypeEnum 
use Bardoqi\Sight\Enums\PaginateTypeEnum 
use App\Repositories\ArticleRepository;
use App\Repositories\UserRepository; 

class ArticlePresenter extents Presenter
{
   use PresenterTrait;

   public function getArticleList($where)
   {
       $articleArray = ArticleRepository::getList($where);
       $user_ids = $this->selectFields('id','title','created_at','created_by')
            ->fromLocal($articleArray,'articles')
            ->pluck('created_by');
       $users = UserRepository::getUsersWithIds($user_ids);
       $this->innerJoinForeign($users,'userss')
            ->onRelationByObject(Relation::of()
                ->localAlias('articles')
                ->localField('created_by')
                ->foreignAlias('users')
                ->foreighField('id')) 
            ->addFieldMappingByObject(FieldMapping::of()
                ->key('created_at')
                ->src('created_at')
                ->type(MappingTypeEnum::METHOD_NAME))
            ->addFieldMappingByObject(FieldMapping::of()
                ->key('created_by')
                ->src('user_name')
                ->type(MappingTypeEnum::JOIN_FIELD));         
       return $this->toPaginateArray(PaginateTypeEnum::PAGINATE_API);
   }
}

然后您可以从控制器中调用它

    return ArticlePresenter::of()->getArticleList($where);    

您还可以在属性中定义映射

    protected $list_mapping = [
         ['created_at' => ['src'=>'created_at', 'type'=>MappingTypeEnum::METHOD_NAME  ]], 
         ['created_by' => ['src'=>'user_name', 'type'=>MappingTypeEnum::JOIN_FIELD  ]],
    ];
    
    // You need to call:
    $this->addFieldMappingList($this->list_mapping);
    

然后created_by将返回数组Users中的user_name字段。

您可能会发现这非常简单。

仅此而已!

文档

类Presenter

Presenter::selectFields($field_lists)
  • 参数 $field_lists

$field_lists可以是数组或以逗号分隔的字符串。

使用此函数,您可以决定哪些字段将显示。

并且您可以使用字段映射更改键名。

Presenter::fromLocal($data_list,$alias,$path)
  • 参数 $data_list

$data_list可以是数组,也可以是Laravel的Collection对象。

  • 参数 $alias

可选参数。$alias是我们之后访问它的名称。

  • 参数 $path

可选参数。当数据来自ElasticSearch时,我们应该指出哪个节点包含我们需要的实际数据。

$path是一个点分隔的字符串。

Presenter::fromLocalItem($data_item,$alias,$data_path)

此函数与fromLocal函数相同。但它仅针对单个项目。因此第一个参数是$data_item而不是$data_list。

Presenter::pluck(...$fields)

获取外键数组。之后,您可以使用外键查询连接数组。

  • 参数 $fields

获取外键数据后,您可以使用"innerJoinForeign"和"outerJoinForeign"与外键数组进行连接。

如果外键连接到相同的 foreign 数组,您可以传递所有本地键并在一次pluck中完成。例如

created_by和updated_by字段都位于User表中。因此,您可以

    $this->pluck('created_by','updated_by')
Presenter::innerJoinForeign($data_list,$alias,$keyed_by)

有关参数$data_list和$alias的详细信息,请参阅:fromLocal()

  • 参数 $keyed_by

$keyed_by是连接数组中外键字段的名称。

我们使用$keyed_by对数组进行排序,以减少性能。

带有连接类型"inner"的函数,类似于数据库,如果外键数组不存在,我们将删除记录。

Presenter::outerJoinForeign($data_list,$alias,$keyed_by)

外连接意味着当外键记录不存在时,我们将保留输出表中的记录。

有关参数的详细信息,请参阅innerJoinForeign

Presenter::onRelation($local_field, $foreign_alias, $foreign_field, $relation_type)

此函数类似于SQL中的"on"短语。

所有属性都是:$local_alias, $local_field, $foreign_alias, $foreign_field, $relation_type $foreign_fieds。您不需要传递$local_alias,因为程序可以从其他函数中获取$local_alias。

  • 参数 $local_field

在 SQL 子句中:on local_table.field_local_name = foreign_table.field_foreign_name。$local_field 只是 'field_local_name'。

  • 参数 $foreign_alias

在 SQL 子句中:on local_table.field_local_name = foreign_table.field_foreign_name。$foreign_alias 是 'foreign_table'。

  • 参数 $foreign_field

在 SQL 子句中:on local_table.field_local_name = foreign_table.field_foreign_name。$foreign_field 是 'field_foreign_name'。

  • 参数 $relation_type

$relation_type 是 RelationEnum 中的值数量

-- HAS_ONE:   There is 1 item in join array.
-- HAS_MANY:   There are many items in join array.
-- HAS_MANY_MERGE: There are many items in join array and must to merge to a field.
-- HAS_MANY_SPLIT: There are many items in join array and must to merge to a field.

对于 HAS_MANY_MERGE AND HAS_MANY_SPLIT,您应该提供一个合并记录的函数。

Presenter::onRelationbyObject(Relation $relation)

该函数与 onRelation 相同。但您应该传递一个 Relation 对象。

请参阅 Class Relation。

Presenter::addFieldMapping($key,$src,$type,$alias)

映射是什么?映射用于字段转换。

  • 参数 $key

映射的唯一名称,同时也是输出字段名。

  • 参数 $src

$src 提供了获取值的来源位置。可能是字段名、函数名或格式化程序名。

  • 参数 $type

$type 指定了获取值的方式。它是 MappingTypeEnum 中的一个数字,包含值。

-- FIELD_NAME : Access the value via field name
-- DATA_FORMATER:Transform the value via method of class DataFormatter.
-- METHOD_NAME: Access the value via method name. You should add member method to the class. The difference of the DataFormatter, DataFormatter could using for many fields. The mapping key of the DataFormatter is both source fieldname and output field name, and the member method is only for one field, and  the key of member method is only output field name.   
-- ARRAY_PATH: Access the value via path of array. for instance: "a.b.c". If the data is from ElasticSearch, Or the data contains the Json fields. And you need to flatten it. You could use ARRAY_PATH.
-- JOIN_FIELD: Access the value from the join array
  • 参数 $alias

    如果字段在连接数组中,为了性能,您应该传递 $alias。

映射的规则如下

  • FIELD_NAME

    – $key:可以是任何字符串;– $src:必须是源字段名;– $type:FIELD_NAME;– $alias:必须是 ''。

  • DATA_FORMATER

    – $key:必须是源字段名;– $src:必须是格式化函数名或 FormatterEnum 的值;– $type:DATA_FORMATER;– $alias:如果字段在连接数组中,则必须提供。

  • METHOD_NAME

    – $key:可以是任何字符串;如果是源字段名,您将获得 $value 参数,否则,您应该调用 $this->getValue() – $src:必须是成员方法名;– $type:METHOD_NAME;– $alias:如果字段在连接数组中,则必须提供。

  • ARRAY_PATH

    – $key:可以是任何字符串;– $src:必须是 JSON 字段路径;根路径节点必须是字段名。– $type:ARRAY_PATH;– $alias:如果字段在连接数组中,则必须提供。

  • JOIN_FIELD

    – $key:可以是任何字符串;– $src:必须是源字段名;– $type:FIELD_NAME;– $alias:必须是连接数组的别名。

Presenter::addFieldMappingByObject(FieldMapping $mapping)

该函数与 addFieldMapping 相同。但您应该传递一个 FieldMapping 对象。

请参阅 Class FieldMapping。

Presenter::addFieldMappingList($mapping_list)

该函数与 addFieldMapping 相同。但 addFieldMapping 只支持添加 1 个映射。addFieldMappingList 支持添加映射数组。

数组格式化器是

       [
            ['key1' => ['src'=>a, 'type'=>b, 'alias'=c  ]],
            ['key1' => ['src'=>a, 'type'=>b   'alias'=c  ]],
       ]
     
Presenter::addFormatter($name,$callback)

向 DataFormatter 添加一个函数。

请参阅 \Bardoqi\Sight\Formatters\DataFormatter 的源代码。该类中包含一些转换函数。

如果您想添加新函数,可以使用 addFormatter;

  • 参数 $name

Formatter 的名称。

  • $callback

转换的函数。

Presenter::toArray()

返回记录列表数组。

Presenter::toItemArray()

返回一条记录。它与 SelectLocalItem 一起使用。

Presenter::toPaginateArray()

返回记录列表数组。

  • 参数 $paginate_type 如果值为 PaginateTypeEnum::PAGINATE_API,则将返回不带链接的数据。否则返回带链接的数据。
Presenter::toTreeArray($parent_id_key)

返回带有树结构的列表。

  • 参数 $parent_id_key

给出 parent_id 字段名。默认值为 'parent_d'。

Presenter::getValue($field_name)

通过映射通过字段名($field_name)获取当前条目的值。

API 响应函数

有一些 API 响应函数

setError(), getError(), setMessage(), getMessage(), setStatusCode(), getStatusCode()

类 FieldMapping

FieldMapping::of()

这是一个创建 FieldMapping 实例的简写函数。

其他函数是用于属性的值。

您可以使用操作符链来进行实例初始化。

例如

    $mapping  = FieldMapping::of()
                ->key('created_at')
                ->src('created_at')
                ->type(MappingTypeEnum::METHOD_NAME);

类关系

Relation::of()

这是一个简写函数,用于创建关系实例。

其他函数是用于属性的值。

您可以使用操作符链来进行实例初始化。

例如

    $relation = Relation::of()
                ->localAlias('articles')
                ->localField('created_by')
                ->foreignAlias('users')
                ->foreighField('id')) ;

类CombineItem

如果您使用函数 $this->getCurrentItem(),它将返回一个CombineItem实例;

CombineItem::getItemValue($field_name,$offset = 0, $alias = null)

获取项目值。

  • param $field_name

您想读取的字段名称

  • $offset

如果关系类型是HAS_MANY, HAS_MANY_MERGE和HAS_MANY_SPLIT,您应该传递$offset。

  • $alias

如果您想读取的字段在连接数组中,您应该传递$alias。

CombineItem::getData($alias)

获取带有连接数组别名的连接数据。它将返回一个IMapItem实例。

接口IMapItem

有两个类实现了接口IMapItem:MultiMapItem, SingleMapitem。

IMapItem::findByPath($path,$offset = null)

从JSON字段中查找值。

如果类是MultiMapItem,您应该传递$offset。

IMapItem::hasColumn($name)

检查字段名称$name是否存在。

IMapItem::getItemValue($key,$offset = null);

从字段获取值。

如果类是MultiMapItem,您应该传递$offset。

异常

有许多异常可以帮助您编码和简化开发。

我们很高兴向您详细介绍

  • 消息:"KeyedBy不能为空!"

    -- 为了减少性能,我们应该使连接数组键化。因此,您必须提供KeyedBy参数。

  • 消息:"KeyedBy $name 不正确!"

    -- 程序发现KeyedBy不是连接数组的字段名称。

  • 消息:"别名不能为空!"

    -- 我们通过别名访问值,因此它不能为空。

  • 消息:"映射键不能为空!"

    -- 我们通过键映射。因此,您应该提供一个字符串键。

  • 消息:"映射源不能为空!"

    -- src属性用于告诉我们在哪里获取值。

  • 消息:"映射类型无效!"

    -- 为了避免错误,您可以使用MappingTypeEnum。

  • 消息:"未找到字段映射列表!"

    -- 可能您已经提供了一个空的映射数组。

  • 消息:"映射数组无效!"

    -- 映射数组的格式如下

       [
            ['key1' => ['src'=>a, 'type'=>b, 'alias'=c  ]],
            ['key1' => ['src'=>a, 'type'=>b   'alias'=c  ]],
       ]
  • 消息:"未找到字段或映射 " . $name . "!"

    -- 我们通过字段名称,映射键名称转换数据。这是SelectFields给出的名称之一,但在数组字段和映射键中找不到。

  • 消息:"函数 " . $name . " 已存在!"

    -- 当您调用AddFormatter函数时,已经存在同名的函数(formatter)。

  • 消息:"项目不是JSON字符串!"

    -- 当调用findByPath时,指定的值应该是JSON字符串,但不是。

常见问题解答

Q:您能告诉我league/fractal和Sight之间的区别吗?

A:league/fractal是最好的数据转换库。使用Sight,您可以使用数组连接。

Q:为什么我们需要定义映射?为什么不用league/fractal这样的数据结构?

A:使用映射,我们可以获取多个函数和格式化程序的多重用途来转换。

故障排除

  • 您不能使用"where"来过滤数组。
  • 您需要学习数组参数的格式。
  • 没有从视图或API层到数据层的转换功能。
  • 不支持嵌套连接。
  • 仅支持相等关系。
  • 其他功能将由您发现。

路线图

计划中

  • 0.1.6版本发布:完成artisan生成器。
  • 0.1.5版本发布:发布详细文档。

已发布

  • 0.1.4 版本:完成 HAS_MANY 和 HAS_MANY_MERGE 的测试
  • 0.1.3 版本:完成 HAS_ONE 和 HAS_MANY_SPILT 的测试。这是一个第一次发布的稳定版本。
  • 0.1.2 RC:添加 HAS_MANY_SPILT 关系。
  • 0.1.1 RC:完成 HAS_ONE 关系。
  • 0.1.0 RC:第一次草案。

贡献

  1. Fork 仓库
  2. 创建 Feat_xxx 分支
  3. 提交你的代码
  4. 创建 Pull Request

捐赠

如果你觉得这个项目有用,你可以给作者买一杯果汁 🍹

donate

许可证

MIT

版权 [2020] Bardo Qi bardoqi@gmail.com