luezoid/laravel-core

这是一个功能丰富的Laravel包,它提供了快速且灵活的方式来快速构建强大的RESTful API。使用此包,可以在模型之间嵌套的复杂关系上进行查询和筛选等操作。更多详情请参阅文档。

v10.1.0 2023-05-24 08:25 UTC

README

Luezoid提供了一种简洁的方法来快速创建API,无需繁琐的操作。使用此包,只需创建少量文件并配置组件,就可以在几分钟内轻松地在Laravel框架中创建简单的CRUD操作。通过深入分析和过去开发者面临的问题,我们开发了一套子框架,旨在简化并加快REST API的构建过程。

此包的一些酷特性包括

  1. 创建CRUD的最简单、最快方法。
  2. 在创建/更新记录之前,预先构建支持定义要特别排除的表列。
  3. 预先构建的搜索和筛选功能,只需配置组件即可使用。
  4. 记录的预构建分页和排序以标准通信格式提供。
  5. API(GET)中的关联数据只是配置问题。
  6. 在操作成功完成后,正确触发事件的更好方法。
  7. 文件上传从未如此简单。可以即时上传到本地文件系统或AWS S3存储桶。
  8. 预构建的丰富服务类,例如EnvironmentServiceRequestServiceUtilityService等。
  9. 可以从代码组件中以简单基于配置的方法查询嵌套相关模型。
  10. 可以在查询参数中以JSON和对象运算符(`->`)的形式传递`SELECT`和关系筛选器,以从表(和在模型中定义的关联对象)中选择特定列,从而生成更少的垃圾数据,而不是每次创建新端点时都编写自定义查询。
  11. 基于简单数组配置的关联对象集的通用/开放搜索。

注意:此存储库中有一个完整的示例,其中包含所有这些功能以及预配置的核心包:luezoidtechnologies/laravel-core-base-repo-example

安装

我们建议使用Composer安装此包。此包支持Laravel版本>=5.x。

composer require "luezoid/laravel-core"		# For latest version of Laravel (>=10.x)
composer require "luezoid/laravel-core:^9.0"	# For Laravel version 9.x
composer require "luezoid/laravel-core:^8.0"	# For Laravel version 8.x
composer require "luezoid/laravel-core:^7.0"	# For Laravel version 7.x
composer require "luezoid/laravel-core:^6.0"	# For Laravel version 6.x
composer require "luezoid/laravel-core:^5.0"	# For Laravel version 5.x

接下来,配置您的 app/Exceptions/Handler.php 文件,并使用 Luezoid\Laravelcore\Exceptions\Handler 来扩展它。示例文件可以在这里查看。

1. 创建 CRUD

使用此包在正常的 MVC 架构中在控制器(Controller)与模型(Model)范式之间增加一个额外的实体。这里所说的这个额外实体被称为 仓库(Repository)。一个 仓库(Repository) 是一个完整的类,其中包含您所有的业务逻辑,从从 控制器(Controller) 获取处理后的数据,到使用 模型(Model)(s)将其保存到数据库。通过使用 仓库(Repository) 作为 控制器(Controller)模型(Model) 之间的中间件,我们旨在在 控制器(Controller) 端保持代码的整洁,并使其成为一个仅接收数据(通常是从视图,通常是 REST 路由)、验证它(如果有的话,我们使用 请求(Request) 类来定义这样的规则)、预处理它(例如,将前端发送的 camelCased 数据转换为 snake_case)并将业务处理后的数据发送回视图的调解者。

让我们从创建一个简单的 小丑(Minions) CRUD 开始。

我们提供了用于表 minions、模型 Minion、控制器 MinionController 和仓库 MinionRepository 的示例迁移。将这些文件添加到您的应用程序中,并相应地调整命名空间。然后在 routes/api.php 中创建一个路由资源,我们就准备好了。

Route::resource('minions', 'MinionController', ['parameters' => ['minions' => 'id']]);

假设您的本地服务器正在 7872 端口运行,尝试调用以下 REST 端点

  1. POST /minions

     curl -X POST \
      https://:7872/api/minions \
      -H 'Content-Type: application/json' \
      -H 'cache-control: no-cache' \
      -d '{
    	"name": "Stuart",
    	"totalEyes": 2,
    	"favouriteSound": "Grrrrrrrrrrr",
    	"hasHairs": true
    }'
    
  2. PUT /minions/1

    curl -X PUT \
      https://:7872/api/minions/1 \
      -H 'Content-Type: application/json' \
      -H 'cache-control: no-cache' \
      -d '{
    	"name": "Stuart - The Pfff",
    	"totalEyes": 2,
    	"favouriteSound": "Grrrrrrrrrrr Pffffff",
    	"hasHairs": false
    }'
    
  3. DELETE /minions/1

    curl -X DELETE \
      https://:7872/api/minions/1 \
      -H 'cache-control: no-cache'
    
  4. GET /minions

    curl -X GET \
      https://:7872/api/minions \
      -H 'cache-control: no-cache'
    
  5. GET /minions/2

    curl -X GET \
      https://:7872/api/minions/2 \
      -H 'cache-control: no-cache'
    

2. 对于默认的 POST 和 PUT 请求排除列

参考 Minon 模型。我们有以下公共属性,可以用来定义一个数组,包含要特别排除的列的列表,用于默认的 POSTPUT CRUD 请求

// To exclude the key(s) if present in request body for default POST CRUD routes eg. POST /minions
public $createExcept = [
	'id'
];

// To exclude the key(s) if present in request body for default PUT CRUD routes eg. PUT /minions/1
public $updateExcept = [
	'total_eyes',
	'has_hairs'
];

首先在模型中使用此类配置的主要优点是:提供了一种干净、优雅的方法,并可以简单地减少在将所有数据保存到表中之前需要执行的编码工作量。典型的例子包括

  1. 如果攻击者发送 POST /users 请求的请求体中包含了 is_email_verified 列表值,您不希望保存该列的值;只需将其添加到 $createExcept 中即可。您无需在代码或请求规则中特别排除该列。
  2. 如果攻击者发送 PUT /users/{id} 请求的请求体中包含了 username 列表值,您不希望更新该列;只需将其添加到 $updateExcept 中即可。您无需在代码或请求规则中特别排除该列。

3. 搜索与过滤

您可以在所有 GET 请求中对表中可用的列进行搜索。让我们从以下示例开始

  • 通用搜索

    默认情况下,表中的所有可用列都已准备好在 GET 请求中进行查询,只需在查询参数中传递键值对即可。但为了在模型本身中明确指出,只需定义一个公共属性 $searchable,它是一个包含允许搜索的列的数组的数组。例如,如果您想搜索所有最喜欢的声音是 Pchhhh 的小丑。

      curl -X GET \
        'https://:7872/api/minions?favouriteSound=Pchhhh' \
        -H 'cache-control: no-cache'
    

    响应应包含所有在表中minionsfavourite_sound列中包含字符串Pchhhh的奴仆。

    搜索使用LIKE运算符,格式为'%-SEARCH-STRING%'

  • 一般过滤

    与搜索类似,您需要定义公共属性$filterable,它是一个包含允许过滤的列的数组。

      public $filterable = [
          'id',
          'total_eyes',
          'has_hairs'
      ];
    

    如果查询参数中存在,将针对这些列执行精确匹配。

    示例:要查找所有totalEyes等于1的奴仆

      curl -X GET \
        'https://:7872/api/minions?totalEyes=1' \
        -H 'cache-control: no-cache'
    

    过滤使用=运算符,格式为'total_eyes'=1

  • 日期过滤器

    将您想要用于日期过滤的列添加到模型类中的$filterable属性。完成后,现在您可以简单地传递查询参数from(AND/OR)to与日期(或日期时间)值,格式为标准MySQL格式(Y-m-d H:i:s)。例如:我们想要搜索所有在2020-04-25 09:25:20之后创建的奴仆

      curl -X GET \
        'https://:7872/api/minions?createdAt=2020-04-25%20%2009:25:20' \
        -H 'cache-control: no-cache'
    

    您还可以在查询参数中传递列名,以便应用日期搜索。只需传递键dateFilterColumn以及您想要使用日期搜索的列名(但请注意,$filterable属性必须指定此列,以便使事情正常工作)。

注意

  1. 您可以在查询参数中指定多个键值对,所有条件都将使用AND运算符进行查询。
  2. 传递所有变量时使用camelCasing,并且所有内容都将内部转换为snake_casing。您可以通过重写属性$isCamelToSnake$isSnakeToCamel并将它们设置为false来配置此转换,在ApiCotroller中实现。

4. 分页与排序

您注意到我们刚刚创建的GET端点的响应了吗?让我们简要地看看。参考响应

{
  "message": null,
  "data": {
    "items": [
      {},
      {},
      ...
    ],
    "page": 1,      // tells us about the current page
    "total": 6,     // tells us the total results available (matching all the query params for searching/filtering applied)
    "pages": 1,     // total pages in which the whole result set is distributed
    "perpage": 15   // total results per page
  },
  "type": null
}

非常直观,不是吗?

您可以通过传递查询参数perpage=5(限制每页大小)。同样,page=2将抓取第2页的结果。

要按特定列对结果集进行排序,只需发送查询参数键orderby以及列名,并使用单独的键order,其值为ASC表示升序(或)DESC表示降序排序。默认情况下,结果按降序排序。

分页和排序结果从未如此简单过 :)。

注意:任何从扩展\Luezoid\Laravelcore\Repositories\EloquentBaseRepository::getAll()的存储库(例如MinionRepository)检索结果的GET(索引)路由都已准备好此类分页和排序。请确保使用此预构建功能并节省手动为每个端点实现这些功能的时间,并拿上一杯啤酒来放松。

5. 关联数据

让我们假设每个Minion都由Gru领导一个任务,即存在Minion与任务之间的一对一关系。查看missions表的迁移(12)和模型Mission。要检索每个Minion的领导任务,在GET请求(索引和显示)中,只需将关系名称添加到MinionController属性中,如下所示

  • GET /minions

    protected $indexWith = [
        'leading_mission'	// name of the hasOne() relationship defined in the Minion model
    ];
    
  • GET /minions/2

    protected $showWith = [
        'leading_mission'	// name of the hasOne() relationship defined in the Minion model
    ];
    

这就完了。只需进行一些配置,你就可以在响应中看到每个 Minion 对象都包含另一个对象 leadingMission,它是一个由相应 Minion 领导的 Mission 模型实例。

注意:对于嵌套关系,你可以通过附加点号(.)操作符来定义它们,例如 employee.designations

6. 在操作成功时附加事件

让我们安排一个 事件,以便在创建由 Minion 领导的新 Mission 时触发,将其带到 Gru 的实验室。在 routes/api.php 中创建一个 POST 路由。

Route::post('missions', 'MissionController@createMission')->name('missions.store');

我们需要为这个路由准备好 MissionControllerMissionRepositoryMissionCreateRequestMissionCreateJob。此外,我们还需要一个事件,比如 BringMinionToLabEvent 准备被触发,并将相同的配置到作业 MissionCreateJob 中的属性 public $event = BringMinionToLabEvent::class; 现在尝试按以下方式调用路由 POST /missions

curl -X POST \
  https://:7872/api/missions \
  -H 'Content-Type: application/json' \
  -H 'cache-control: no-cache' \
  -d '{
	"name": "Steal the Moon! Part - 4",
	"description": "The first moon landing happened in 1969. Felonius Gru watched this historic moment with his mother and was inspired by the landing to go to outer space just like his idol Neil Armstrong.",
	"minionId": 2
}'

你应该能在 storage/logs/laravel.log 文件中看到一条日志条目,这是我们在事件 BringMinionToLabEvent 中设置的。

7. 文件上传

使用这个包,文件上传只是一个配置问题。使用以下命令将配置文件 file.php 发布到配置目录:

php artisan vendor:publish --tag=luezoid-file-config

根据您的需求配置新的 type 代表特定模块,例如 MINION_PROFILE_PICTURE,定义 validation(如果有),允许的 valid_file_typeslocal_path(用于本地文件系统)等。默认添加了一个名为 EXAMPLEtype 作为参考。接下来,在 AppServiceProvider 中添加以下代码:

$this->app->bind(\Luezoid\Laravelcore\Contracts\IFile::class, function ($app) {
    if (config('file.is_local')) {
        return $app->make(\Luezoid\Laravelcore\Files\Services\LocalFileUploadService::class);
    }
    return $app->make(\Luezoid\Laravelcore\Files\Services\SaveFileToS3Service::class);
});

接下来,创建以下路由:

Route::post('files', '\Luezoid\Laravelcore\Http\Controllers\FileController@store')->name('files.store');

现在,你可以上传文件了。

curl -X POST \
  https://:7872/api/files \
  -H 'cache-control: no-cache' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F type=EXAMPLE \
  -F file=@/home/choxx/Desktop/a61113f5afc5ad52eb59f98ce293c266.jpg

响应体将包含一个 idurl 以及表示上传文件存储位置和其他细节的几个其他字段。请参阅示例 这里。你可以使用这个 id 字段将其存储在您的表中作为外键列,并与记录建立 hasOne() 关系,然后根据需要使用它们。

此外,在配置文件本身中,你可以配置是否要使用本地文件系统进行上传或使用 AWS S3 存储桶。要使用 S3 存储桶,您需要在 .env 文件中配置 AWS 凭据。

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=

不是太棒了吗? :)

10. 选择和关系过滤器 - 对表列(以及嵌套关系)的 SELECTWHERE 查询

这是本包最酷的功能之一。厌倦了编写查询来缩短结果集并对嵌套关系数据进行过滤吗?只需发送简单的查询参数即可应用此类过滤器。让我们看看如何操作。

  • 选择过滤器

    在查询参数中,可以通过键值对传递,以限制特定模型对象及其相关模型实体的响应中列的数量。要发送的键是 selectFilters,其值必须是下面解释的压缩JSON。

    k 是键,r 是关系,cOnly 是设置是否只需要计数还是整个关系数据的标志。可以在嵌套的 r 关系中使用 cOnly 标志。

    {
      "cOnly": false,
      "k": ["id", "name", "favouriteSound"],
      "r": {
        "missions": {
          "k": ["name", "description"]
        }
      }
    }
    

    因此,使用上述JSON,一个如下的GET请求

    curl -X GET \
      'https://:7872/api/minions?selectFilters={%22cOnly%22:false,%22k%22:[%22id%22,%22name%22,%22favouriteSound%22],%22r%22:{%22missions%22:{%22k%22:[%22name%22,%22description%22]}}}' \
      -H 'cache-control: no-cache'
    

    将只返回 minions 表的 idnamefavourite_sound 列,以及 missions 表的 namedescription 列。在 响应 中删除了不需要的冗余列,从而显著减少了整体响应的大小,并加快了API的响应时间。

    注意:主表和相关关系中的 '本地键' 和 '外键' 必须都存在。整个配置可以像第一个关系一样深入。

  • 关系过滤器

    假设我们想找到所有名字为 "Steal the Moon!"Leading Mission 的 minions。使用 Eloquent 查询,我们可以检索到这样的结果

    $query = Minion::whereHas('missions', function ($q) {
        $q->where('name', 'Steal the Moon!');
    });
    

    看起来很简单,但等等,为了使其动态,您需要通常传递包含此 missions.name 列的查询参数,并自己编写自定义逻辑来实现这样的过滤。但通过使用此包,您只需像我们在 搜索和过滤器 中看到的那样发送查询参数即可。您只需发送查询参数作为 relation-name->column-name={string-to-be-filtered}。请看下面的示例

    curl -X GET \
      'https://:7872/api/minions?missions-%3Ename=Steal%20the%20Moon%21' \
      -H 'cache-control: no-cache'
    

    注意,我们传递了查询参数 missions-%3Ename=Steal%20the%20Moon%21,因此根本不需要编写上述自定义逻辑。

    那么,我们值得一个 星级 评分吗? :)

    注意:这些选择和关系过滤器可以结合使用,以减少响应大小以及后端代码的大小。结合使用这两个功能,制作出美妙的应用程序。

11. 通用/开放搜索

需要通过查询参数发送键 searchKey 及其值。
在仓库中定义配置以及要搜索的模型和列名。
用法

$searchConfig = [  
    'abc' => [  
        'model' => Abc::class,  
        'keys' => ['name', 'field']  
    ],  
    'groups' => [
        'model' => ProductGroup::class,  
        'keys' => ['name', 'code', 'hsn_code']  
   ]
];  
return $this->search($searchConfig, $params);

许可证

Laravel-core 在 MIT 许可证下发布。有关详细信息,请参阅捆绑的 LICENSE