anexia/laravel-basemodel

一个Laravel模块,提供特质以扩展Eloquent模型的过滤、搜索、排序、分页等功能

1.0.1 2019-01-15 11:59 UTC

This package is auto-updated.

Last update: 2024-09-17 20:50:31 UTC


README

一个Laravel包,用于为Eloquent模型提供扩展的基本功能(过滤、排序、分页)。

1. 安装和配置

通过composer安装模块,因此需要修改你的composer.json中的require部分

"require": {
    "anexia/laravel-basemodel": "1.0.0"
}

现在运行

composer update [-o]

将包的源代码添加到你的/vendor目录,并更新自动加载。

2. 使用

2.1. 模型

在所有需要支持基本功能(过滤、排序、分页等)的模型中使用BaseModelInterface和BaseModelTrait。

// model class app/Post.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements BaseModelInterface
{
    use BaseModelTrait;
    
    // additional model functionality can be added
}
// auth model class app/User.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;

class User extends Model implements BaseModelInterface,
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, BaseModelTrait;
    
    ...
}

2.2. 控制器

使用BaseModelController允许在多级对象关系上执行“批量操作”(创建、更新、删除)。它包含一个名为'editObjectContents'的方法,该方法期望一个BaseModel(实现BaseModelInterface并使用BaseModelTrait)以及新的属性(作为数组,由POST/PUT/PATCH请求提供)。

它使用SubTransactionServiceProvider提供的嵌套事务*,这允许更好地控制一次多相关模型上的数据。

  • 嵌套事务目前仅适用于PostgreSQL连接(通过Anexia\BaseModel\Database\PostgresConnection)。要支持其他数据库,需要提供新的连接类,这些类扩展了Anexia\BaseModel\Database\Connection以处理多个打开的事务。

3. 模型配置方法

BaseModelInterface要求每个模型都有几个内部配置,其中大多数默认为空(如BaseModelTrait中的许多)。但是,如果需要,可以对这些配置方法进行特定的修改,以使任何模型在验证、更改行为和其他方面非常自给自足。

3.1. getUnmodifiable

预期结果:不可变属性的数组

此函数返回的所有属性都将从BaseModelController的'editObjectContents'方法中排除。因此,这些属性将不会自动/批量编辑,必须显式设置/更改。

3.2. getDefaults

预期结果:具有默认值的属性数组

如果在该方法中为属性分配了值,则BaseModel构造函数将在模型实例化时自动填充它们(无论它们是否定义为$guarded或$fillable)。它们不需要显式设置。

3.3. getDefaultSearch

预期结果:在调用allExtended时,默认搜索的属性数组

这些参数可以是方法调用时传递的(setSearches和setOrSearches)或通过HTTP请求(通过request()->all()获取并作为allExtended方法中的$searches和$orSearches处理)。getDefaultValueSearch函数返回的所有属性都将使用给定的子字符串('WHERE x LIKE "y" 'SQL条件)进行搜索,搜索将以OR连接。

有关搜索行为的更多详细信息,请参阅搜索部分。

3.4. getDefaultSorting

预期结果:在调用allExtended时默认排序的所有属性及其方向

有关排序行为的更多详细信息,请参阅排序部分。

3.5. getRelationships

预期结果:与相关模型类关联的所有属性。可能的输入:布尔型 $list 参数

如果 $list 为真,此方法应返回一个包含所有关系属性及其相关类的简单数组。

示例

// from model class app/Post.php
    
    $fillable = ['name', 'type', 'author_id'];
    // $guarded / etc.
    
    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }
    
    /**
     * @param boolean|false $list
     * @return array
     */
    public static function getRelationships($list = false)
    {
        if ($list) {
            return [
                'author' => User::class
            ];
        }
        
        // return something else for $list = false
    }
}

如果 $list 是 false,则此方法应返回模型关系的更复杂表示。它应该根据关系的性质返回一个包含 'one' 和 'many' 关系的多元数组。此外,每个关系属性不仅应包含相关类的名称,还应包含对应模型上相应属性的名称(反向方面),以及从当前模型一侧是否可编辑和可空。

示例

// from model class app/Post.php
    
    $fillable = ['name', 'type', 'author_id'];
    // $guarded / etc.
    
    public function author()
    {
        return $this->belongsTo(User::class, 'author_id');
    }
    
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
    
    /**
     * @param boolean|false $list
     * @return array
     */
    public static function getRelationships($list = false)
    {
        if ($list) {
            // return list
        }

        return [
            'one' => [
                'author' => [
                    'model' => User::class,
                    'inverse' => 'posts',
                    'editable' => false, // true by default
                    'nullable' => false // true by default
                ]
            ],
            'many' => [
                'comments' => [
                    'model' => Comment::class,
                    'inverse' => 'post'
                ]
            ]
        ];
    }
}

根据模型关系的配置,BaseModelController 的 'editObjectContent' 方法将遍历相关对象(及其关系、及其关系等)并对其进行更改(创建或更新)。

3.6. getValidationRules

只有在你需要特殊逻辑时才应重写它,否则可以使用 $validationRules 提供这些规则。

预期结果:与相关模型类相关联的所有 Laravel 验证规则(有关支持的规则的详细信息,请参阅 https://laravel.net.cn/docs/5.3/validation),可能的输入:布尔型 $checkCompletion 参数

如果 $checkCompletion 为 true,$validationRules 将以前缀所需属性。

如果 $checkCompletion 为 true,此方法应返回所有属性及其所有必要的验证规则。

然而,如果 $checkCompletion 为 false,则返回的规则应仅支持可编辑属性的部分存在,以支持 PATCH 请求。

示例 1

// from model class app/Post.php

    $fillable = ['name', 'type', 'author_id'];
    protected static $validationRules = [
        'name'      => 'string|min:1|max:255',
        'type'      => 'string|nullable',
        'author_id' => 'integer|nullable'
    ];
}

示例 2

// from model class app/Post.php
    
    $fillable = ['name', 'type', 'author_id'];
    
    /**
     * @param bool|true $checkCompletion
     * @return array
     */
    public static function getValidationRules($checkCompletion = true)
    {
        if ($checkCompletion) {
            return [
                'name' => 'required|string|min:1|max:255',
                'type' => 'required|string|nullable',
                'author_id' => 'required|integer|nullable'
            ];
        }

        return [
            'name' => 'string|min:1|max:255',
            'type' => 'string|nullable',
            'author_id' => 'integer|nullable'
        ];
    }
}

在上面的示例中,当 $checkCompletion = false 时,不会返回 'required' 规则,因为可能只有现有帖子的名称被更新。如果应用了 'required' 规则,则类似以下请求将导致错误信息,指出 'type' 和 'author_id' 字段缺失,即使它们简单地不应该更改。

PATCH /posts/1

{
    "id": 1,
    "name": "a new post name!"
}

3.7. validateAttributeLogic

预期结果:无或异常(如果自定义验证的任何部分失败)

对于更复杂/逻辑检查,Laravel HTTP 参数验证可能不够用,例如当属性高度依赖于其他对象的关联时。因此,BaseModelController 的 'editObjectContents' 方法调用模型的 'validateAttributeLogic' 方法,该方法在对象属性更新后,但在更改存储到数据库之前充当钩子。

有关包的异常处理更详细的信息,请参阅 异常 部分。

有关模型属性(以及相关对象的必要条件)的所有自定义检查和验证都可以放在 'validateAttributeLogic' 中,并在验证失败时抛出适当的异常。所有可能抛出的异常都将被 'editObjectContents' 方法捕获并转换为 BulkValidationException。

有关包的异常处理的详细信息,请参阅 异常 部分。

4. 可用功能

BaseModelTrait 提供了几个实用方法来支持你的模型和控制器操作。一旦正确配置了模型,许多基本行为将自动发生或需要最少的编码工作。

4.1. 模型默认值

BaseModelTrait 提供了在对象实例化时预填充默认值的功能。可以配置 'getDefaults' 方法以返回模型属性的默认值数组,例如。

// model class app/Post.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements BaseModelInterface
{
    use BaseModelTrait;
    
    $fillable = ['name', 'type'];
    // $guarded / etc.
    
    /**
     * @param Model|null $currentUser
     * @return array
     */
    public static function getDefaults(Model $currentUser = null)
    {
        return [
            'type' => 'blog'
        ];
    }
}

4.2. 关系配置(批量操作)

模型可以使用其关系进行 '批量操作'。这些批量操作允许在单个请求中管理多个相关模型,而不是对每个模型操作调用一个请求。

如果 Post 和 Comment 类之间存在一对一的关联(一个帖子可以有多个评论),并且 Post 中的 'comments' 关系被配置为可编辑,则对 Post 端点的更新/创建也将更新/创建给定的评论。

以下场景详细描述了此用例。

  1. Post 模型定义了其与 Comment 的关系为可编辑(对 Post 的更改请求可以“向下传递”并触发其一个或多个评论的变化)
// model class app/Post.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements BaseModelInterface
{
    use BaseModelTrait;
    
    $fillable = ['name'];
    // $guarded / etc.
    
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
    
    /**
     * @param boolean|false $list
     * @return array
     */
    public static function getRelationships($list = false)
    {
        if ($list) {
            return [
                'comments' => Comment::class
            ];
        }

        return [
            'many' => [
                'comments' => [
                    'model' => Comment::class, // related model's class
                    'inverse' => 'post', // name of the relation within the related model
                    'editable' => true, // true by default
                    'nullable' => true // true by default
                ]
            ]
        ];
    }
}
  1. 评论模型定义了它与帖子模型的不可编辑关系(评论的更改请求不能向上传递并触发其帖子中的更改)。
// model class app/Comment.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model implements BaseModelInterface
{
    use BaseModelTrait;
    
    $fillable = ['text];
    // $guarded / etc.
    
    public function post()
    {
        return $this->belongsTo(Post::class);
    }
    
    /**
     * @param boolean|false $list
     * @return array
     */
    public static function getRelationships($list = false)
    {
        if ($list) {
            return [
                'post' => Post::class
            ];
        }

        return [
            'one' => [
                'post' => [
                    'model' => Post::class,
                    'inverse' => 'comments',
                    'editable' => false,
                    'nullable' => false
                ]
            ]
        ];
    }
}
  1. 如果现在发生了一个新的帖子POST请求,其中包含定义的关系字段'comments','comments'中的所有内容都将存储为评论对象,例如:
POST /posts

{
    "name":"Post 1"
    "comments":[
        {
           "text":"A comment from a user" 
        },
        {
           "text":"Another comment from a user" 
        }
    ]
}

此请求将创建一个名为'Post 1'的新帖子以及两个新的评论,内容分别为'来自用户的评论'和'来自用户的另一个评论',无需调用两个额外的POST请求来创建这两个评论。

4.3. 扩展所有和查找方法

BaseModelTrait实现了两个改进的模型方法'allExtended'和'findExtended'。它们是eloquent模型的'all'和'find'方法的扩展版本,其行为如下

4.3.1. 静态方法allExtended

此方法将BaseModel功能添加到每个eloquent模型的基本'all'方法中

* filter
* sorting
* pagination
* inclusion of related objects

尽管所有这些功能都可以通过GET请求参数进行配置 - 请参阅HTTP列表请求选项和参数部分,了解有关请求期间可能参数配置的更多详细信息 - 其中一些也可以在类(例如REST控制器)中调用'allExtended'方法时预先填充。BaseModelInterface的方法定义如下

public static function allExtended($extendedParameters = null);

'columns'和'included'数组将影响要返回的模型属性和关系。它们不影响SQL查询本身,但会影响resultSet(通过Illuminate\Pagination\LengthAwarePaginator接收)的表示。

'filters'和'searches'数组将直接影响SQL查询本身,并将累积如下:SELECT ... WHERE ( ( $filters AND (GET-filters from request) ) OR $orFilters ) AND ( $searches OR $orSearches )

以下部分将描述如何使用'allExtended'ExtendedModelParameters方法直接在方法调用中使用,而不管可能的HTTP请求参数。

4.3.1.1. 参数

因此,与'add'方法一样,在检索对象组时可以指定以下配置:

4.3.1.1.1 setColumns

普通数组,定义了要返回所有找到的对象的列(=模型的属性)。对象ID始终返回,无论'columns'变量的设置如何。示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setColumns(['name']);
Post::allExtended($extendedParameters);

将仅返回所有找到的帖子条目的名称(和ID)

{
	"data": [
		{
			"id": 1,
			"name": "Name Post 1"
		},
		{
			"id": 2,
			"name": "Name Post 2"
		}, ...
	],
	// pagination information fields ...
}
4.3.1.1.2. setFilters

多级数组,包含'WHERE x = y'过滤条件,嵌套如下

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setFilters([
    // AND connected conditions go here (single condition or array)
    [
        // OR connected conditions go here (single conditions or array)

            // AND connected conditions go here (single condition or array)
        ], ...
    ], ...
]);

示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setFilters([
   [
       [
           'author_id' => null,
           'catgory_id' => null,
       ],
       'author_id' => $curUser->id,
       'category.genre.supervisor_id' => $curUser->id
   ],
   'author_id' => 2,
]);
Post::allExtended($extendedParameters);

将导致以下SQL查询

select * from posts where (
    (
        (
            (
                ((author_id is null and catgory_id is null)) 
                or author_id = $curUser->id 
                or exists (
                    select * from categories where posts.catgory_id = categories.id 
                    and exists (
                        select * from genres where categories.genre_id = genres.id 
                        and supervisor_id = $curUser->id
                    )
                )
            )
        ) 
        and author_id = 2
    )
)
4.3.1.1.3. setOrFilters

多级数组,包含'WHERE ... OR x = y'过滤条件,嵌套如下

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setOrFilters([
    // OR connected conditions go here (single condition or array)
    [
        // AND connected conditions go here (single condition or array)
        [
            // OR connected conditions go here (single condition or array)
        ], ...
    ], ...
]);

示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setOrFilters([
   [
       [
           'author_id' => null,
           'catgory_id' => null,
       ],
       'author_id' => $curUser->id,
       'category.genre.supervisor_id' => $curUser->id
   ],
   'author_id' => 2,
]);
Post::allExtended($extendedParameters);

将导致以下SQL查询

select * from posts where (
    (
        (
            (
                ((author_id is null or catgory_id is null)) 
                and author_id = $curUser->id 
                and exists (
                    select * from categories where posts.category_id = categories.id 
                    and exists (
                        select * from genres where categories.genre_id = genres.id
                        and supervisor_id = $curUser->id
                    )
                )
            )
        ) 
        or author_id = 2
    )
)
4.3.1.1.4. setIncludes

普通数组,包含所有模型关系(它们的名称)要包含在结果列表中。

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setIncludes(['comments', 'author']);

示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setIncludes(['comments', 'author'])->setColumn(['name']);
Post::allExtended($extendedParameters);

将返回所需关系的属性以及找到的帖子条目

{
	"data": [
		{
			"id": 1,
			"name": "Name Post 1",
			"comments": [
			    {
			        "id": 1,
			        "post_id": 1,
			        "text": "A comment text"
			    },
			    {
                    "id": 2,
                    "post_id": 1,
                    "text": "Another comment text"
                } 
			],
			"author": {
			    "id": 1,
			    "name": "A User"
			}
		},
		{
			"id": 2,
			"name": "Name Post 2",
            "comments": [
                {
                    "id": 3,
                    "post_id": 2,
                    "text": "Some comment text"
                }
            ],
            "author": {
                "id": 1,
                "name": "A User"
            }
		}, ...
	],
	// pagination information fields ...
}
4.3.1.1.5. setSearches

多级数组,包含'WHERE X LIKE "y"'过滤条件,嵌套如下

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setSearches([
    // AND connected conditions go here (single condition or array)
    [
        // OR connected conditions go here (single conditions or array)
    ], ...
]);

示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setSearches([
    'name' => ['%post%', 'blog%', '%entry'],
    'author_id' => '%1%'
]);
Post::allExtended($extendedParameters);

将导致以下SQL查询

select * from posts where (((name LIKE '%post%' or name LIKE 'blog%' or name LIKE '%entry') and author_id LIKE '%1%'))

由于PostgreSQL仅支持LIKE和ILIKE(不区分大小写的LIKE)查询,对于PostgreSQL连接,查询将是

select * from posts where (((name::TEXT ILIKE '%post%' or name::TEXT ILIKE 'blog%' or name::TEXT ILIKE '%entry')
    and author_id::TEXT ILIKE '%1%'))
4.3.1.1.6. setOrSearches

多级数组,包含'WHERE ... OR X LIKE "y"'过滤条件,嵌套如下

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setOrSearches([
    // OR connected conditions go here (single condition or array)

    [
        // AND connected conditions go here (single conditions or array)
    ], ...
]);

示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setOrSearches([
    'name' => ['%post%', 'blog%', '%entry'],
    'author_id' => '%1%'
]);
Post::allExtended($extendedParameters);

将导致以下SQL查询

select * from posts where (((name LIKE '%post%' and name LIKE 'blog%' and name LIKE '%entry') or author_id LIKE '%1%'))

由于PostgreSQL仅支持LIKE和ILIKE(不区分大小写的LIKE)查询,对于PostgreSQL连接,查询将是

select * from posts where (((name::TEXT ILIKE '%post%' and name::TEXT ILIKE 'blog%' and name::TEXT ILIKE '%entry')
    or author_idvLIKE '%1%'))
4.3.1.1.7 setPagination(第七个参数)

整数或null以避免模型默认分页。不能超过模型的最大分页值(自动减少)。

4.3.1.1.8 setDecryptionKey(第八个参数)

字符串或null,用于使用anexia/encryption包的模型解密加密属性。如果提供了正确的解密密钥,加密属性将自动解密并返回。如果没有提供解密密钥,加密属性将不包括在响应中。

4.3.2. 静态方法 findExtended

此方法将 BaseModel 功能添加到每个 eloquent 模型的基本 'find' 方法

* filter
* inclusion of related objects

尽管所有这些功能都可以通过 GET 请求参数进行配置 - 请参阅HTTP 列表请求选项和参数部分以获取有关请求期间可能参数配置的更多详细信息 - 其中一些在类(例如 REST 控制器)中调用 'findExtended' 方法时也可以预先填充。BaseModelInterface 的方法定义如下

public static function findExtended($id, $extendedParameters = null);

'columns' 和 'includes' 数组将影响返回的模型属性和关系。它们不影响 SQL 查询本身,但会影响结果的表示。

'filters' 数组将直接影响 SQL 查询本身,并将按如下方式累积:SELECT ... WHERE $filters

以下部分将描述如何直接在方法调用中使用 'findExtended' ExtendedModelParameters 方法,而不管可能的 HTTP 请求参数如何。

4.3.2.1. 参数

因此,与'add'方法一样,在检索对象组时可以指定以下配置:

4.3.2.1.1. setColumns(第二个参数)

定义要返回的列(即模型的属性)的普通数组。无论 'columns' 变量的设置如何,对象 ID 总是返回。示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setColumns(['name']);
Post::findExtended(1, $extendedParameters);

将仅返回找到的帖子条目的名称(和 ID)

{
	"data": {
        "id": 1,
        "name": "Name Post 1"
    }
}
4.3.2.1.2. setFilters(第三个参数)

多级数组,包含'WHERE x = y'过滤条件,嵌套如下

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setFilters([
    // AND connected conditions go here (single condition or array)
    [
        // OR connected conditions go here (single conditions or array)
        [
            // AND connected conditions go here (single condition or array)
        ], ...
    ], ...
]);

示例

$extendedParameters = new ExtendedModelParameters();
$extendedParameters->setFilters([
    [
        [
            'author_id' => null,
            'catgory_id' => null,
        ],
        'author_id' => $curUser->id,
        'category.genre.supervisor_id' => $curUser->id
    ],
    'author_id' => 2
]);
Post::findExtended(1, $extendedParameters);

将导致以下SQL查询

select * from posts where id = 1 and 
4.3.2.1.3 setIncludes(第四个参数)
4.3.2.1.4 setPagination(第五个参数)

整数或null以避免模型默认分页。不能超过模型的最大分页值(自动减少)。

4.3.2.1.5 setDecryptionKey(第六个参数)

字符串或null,用于使用anexia/encryption包的模型解密加密属性。如果提供了正确的解密密钥,加密属性将自动解密并返回。如果没有提供解密密钥,加密属性将不包括在响应中。

4.4. 异常

BaseModel 包包含两个内置异常类

4.4.1. SqlException

当 BaseModelController 的 'editObjectContents' 方法捕获到标准的 QueryException(Illuminate\Database)时,它会查找特定的 PostgreSQL 错误代码,并将它们转换为带有默认状态码 400 和相应文本 'message' 的 SqlExceptions。

4.4.2. BulkValidationException

当 BaseModelController 的 'editObjectContents' 方法捕获任何异常时,它会将它们的消息(或消息,如果在编辑多个相关对象的过程中抛出多个异常)放入 BulkValidationException 的 'messages' 字段。BulkValidationException 默认状态码为 400,并且有一个 'message' 字段(默认情况下为 'Error in bulk validation')和一个 'messages' 字段(可在 'getMessages' 方法中访问),其中包含收集到的异常消息和有关实际发生的错误的更多详细信息。

4.5. HTTP 列表请求选项和参数

4.5.1. 排序

每个提供多个实体列表的端点请求都可以根据相关实体的属性进行排序。

BaseModel 为列表请求提供默认排序(无特定 ID 的 GET,例如:GET /posts)。在 BaseModel 中,可以通过 getDefaultSorting 方法定义排序的字段和方向。

// model class app/Post.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements BaseModelInterface
{
    use BaseModelTrait;
    
    $fillable = ['name', 'type'];
    ...
    
    /**
     * @return array
     */
    public static function getDefaultSorting()
    {
        return [
            'name' => 'ASC'
        ];
    }
}

上述示例将始终返回按名称升序排序的帖子列表。如果为默认排序定义了多个字段,则将按从上到下的顺序处理它们(顶部第一个,底部最后一个)。

示例 以下定义

    /**
     * @return array
     */
    public static function getDefaultSorting()
    {
        return [
            'name' => 'ASC',
            'type' => 'DESC'
        ];
    }

导致帖子列表按名称升序和类型降序排序。

4.5.1.1. 自定义排序

可以通过 GET 参数修改默认的 sort_field 和 sort_direction

  • sort_field - 修改内部 SQL 查询的 'orderBy' 参数
  • sort_direction - 修改内部 SQL 查询的 'orderBy DESC|ASC' 参数
  • default_sorting - boolean,用于在每个端点切换默认排序的开/关(默认为开启)

'sort_field' 参数可以是请求的实体的任何属性。每个 'sort_field' 的 'sort_direction' 默认为 'ASC'。要更改它,参数必须始终包含相关属性作为键

GET /posts?sort_direction[name]=desc&default_sorting=0    // will work

GET /posts?sort_direction=desc           // will NOT work
GET /posts?sort_direction[]=desc         // will NOT work

多个'sort_field'和'sort_direction'参数可以组合使用,以创建多级排序结果。'sort_field'参数的给定顺序将定义内部SQL查询中的排序顺序,例如:

GET /posts?sort_field[]=title&sort_field[]=first_name&sort_direction[title]=desc&default_sorting=0

将产生以下内部SQL查询

select * from `posts` order by `title` desc, `first_name` asc

如果给出了没有对应'sort_field'参数的'sort_direction'参数,它们将按照给定顺序添加到'sort_field'参数条件之后,例如:

GET /posts?sort_direction[name]=desc&sort_field[]=type&sort_field[]=name&sort_direction[type]=desc&default_sorting=0

将产生以下内部SQL查询

select * from `posts` order by `type` desc, `name` asc

尽管'sort_direction[name]'参数在'sort_field[]=type'之前,但它将被添加之后,因此排序结果的顺序是:

  • 所有'sort_field'参数(按URI中的顺序)及其相应的'sort_direction'参数
  • 所有剩余的(没有对应'sort_field'参数)'sort_direction'参数(按URI中的顺序)

4.5.2. 过滤

每个提供多个实体列表的端点请求都可以进行过滤,以仅返回具有给定属性所需值的那些结果。

示例

GET /posts?author_id=1将仅返回ID为1的作者的帖子

4.5.2.1. AND过滤(多个过滤器)

过滤器通常通过AND约束添加,因此可以在一个请求中组合多个过滤器。

示例

GET /posts?author.name=Someone&type=SomeType将仅返回类型为'SomeType'且属于名为'Someone'的作者的帖子

4.5.2.2. OR过滤(同一过滤器的多个有效值)

要允许一个过滤器有多个可能的值,可以使用OR约束,将过滤器作为一个数组。

示例

GET /posts?name[]=test post&name[]=Another post将仅返回名称为'test post'或'Another post'的帖子。

4.5.2.3 准备好的过滤器

一些模型带有预定义的过滤器语句来支持某些查询。每个端点部分都列出了现有的预定义过滤器。要调用它们,只需使用"prepared_filter" GET参数

示例 假设,帖子模型有一个名为"current_comments"的过滤器定义

/**
 * @return array
 */
public static function getPreparedFilters()
{
    return [
        'current_comments' => ['year' => 2017, 'type' => 'comment'],
    ];
}

GET /posts?prepared_filter[]=current_comments将仅返回类型为"comment"且属性year=2017的帖子

预定义过滤器可以像其他任何过滤器一样组合

示例 GET /posts?prepared_filter[]=current_comments&name=post_name将仅返回属性year=2017且名称为"post_name"的帖子

注意 预定义过滤器"current_comments"给出的输出与查询year=2017&type=comment相同。预定义过滤器只适用于具有多个过滤值的查询。

预定义复杂过滤器

除了"简单"的预定义过滤器,它们仅代表直接的AND / OR过滤器的组合之外,模型还可以包含更复杂的过滤器结构,包括连接或子查询。

示例 假设,帖子模型有一个名为"name_shorter_10"的过滤器定义

/**
 * @return array
 */
public static function getPreparedComplexFilters()
{
    return [
        'name_shorter_10' => [
            'whereRaw' => ['LENGTH(name) < 10']
        ],
    ];
}

GET /api/v1/bricks?prepared_filter[]=name_shorter_10将仅返回所有名称少于10个字符的帖子

预定义复杂过滤器可以像其他任何过滤器一样组合

示例 GET /api/v1/bricks?prepared_filter[]=name_shorter_10&year=2017仅返回所有名称少于10个字符且属性year=2017的帖子

4.5.3. 搜索

要触发不区分大小写的'LIKE' SQL搜索(不区分大小写的LIKE),可以使用'search' GET参数。如果没有给出搜索参数的显式属性名,则默认搜索'getDefaultSearch'方法中定义的模型属性。

// model class app/Post.php

<?php

namespace App;

use Anexia\BaseModel\Interfaces\BaseModelInterface;
use Anexia\BaseModel\Traits\BaseModelTrait;
use Illuminate\Database\Eloquent\Model;

class Post extends Model implements BaseModelInterface
{
    use BaseModelTrait;
    
    $fillable = ['name', 'type'];
    ...
    
    /**
     * @return array
     */
    public static function getDefaultSearch()
    {
        return [
            'name',
            'type'
        ];
    }
}

示例

GET /posts?search=test将返回所有名称LIKE '%test%'或类型LIKE '%test%'的帖子。

GET /posts?search[type]=test将返回所有类型LIKE '%test%'的帖子。

4.5.3.1. 搜索字段开头或结尾

要查找字段开头的子字符串,可以使用'search_start' GET参数。它也可以用于默认搜索属性或特定属性

示例

GET /posts?search_start=test 将返回所有名称 LIKE 'test%' 或类型 LIKE 'test%' 的帖子。

GET /posts?search_start[type]=test 将返回所有类型 LIKE 'test%' 的帖子。

同样适用于 'search_end' GET 参数,可用于查找字段开头的子串

示例

GET /posts?search_end=test 将返回所有名称 LIKE '%test%' 或类型 LIKE '%test%' 的帖子。

GET /posts?search_end[type]=test 将返回所有类型 LIKE '%test%' 的帖子。

4.5.3.2. AND 搜索(多个搜索条件)

当在 GET 请求中给出多个 'search'、'search_start'、'search_end' 参数时,它们在结果查询中是 AND 连接的。

示例

GET /posts?search=test&search_end[type]=foo 将返回所有 ((名称 LIKE '%test%' 或类型 LIKE '%test%') AND 类型 LIKE '%foo%') 的帖子。

4.5.3.3. OR 搜索(同一搜索条件的多个有效值)

为了定义同一字段搜索的多个可能值,可以将值排列为数组

示例

GET /posts?search[]=test&search[]=foo 将返回所有名称 LIKE '%test%' 或类型 LIKE '%test%' 或名称 LIKE '%foo%' 或类型 LIKE '%foo%' 的帖子。

可以在一个 GET 请求中应用多个 AND 和 OR 搜索过滤器的组合,以创建复杂的搜索。

示例

GET /posts?search[]=test&search_end[type]=foo&search[]=foo&search_start[name]=bar 将返回所有 ((名称 LIKE '%test%' 或类型 LIKE '%test%' 或名称 LIKE '%foo%' 或类型 LIKE '%foo%') AND 类型 LIKE '%foo' AND 名称 LIKE 'bar%') 的帖子。

4.5.4. 分页

对于模型列表的 GET 请求(例如 GET /posts),默认分页始终为每页 10 项,从第 1 页开始(项 0 - 10)。

分页响应始终显示以下结构

* current_page; currently shown page; default = 1
* data; array of all items found for the current page
* from; index of the first item on the current page
* last_page; last available page for the current list
* next_page_url; null or the http url to the next page (with GET param 'page=<nextpage>')
* path; current page's url
* prev_page_url; null or the http url to the previous page (with GET param 'page=<previouspage>')
* to; index of the last item on the current page
* total; sum of all items for the current list (over all pages)

要更改应用程序中的默认分页,可以使用 BaseModel 的 'setPagination' 方法。

要直接在请求中更改每页显示的项目数,请使用 GET 参数 'pagination'。要更改当前显示的页码,请使用 GET 参数 'page',但请注意:如果给定的页码超过当前列表可用的页数(大于 'last_page'),则不会返回错误响应,而是一个有效的响应,其中项目集合为空('data')。

示例

GET /posts?pagination=100&page=2 将返回所有 ((名称 LIKE '%test%' 或类型 LIKE '%test%' 或名称 LIKE '%foo%' 或类型 LIKE '%foo%') AND 类型 LIKE '%foo' AND 名称 LIKE 'bar%') 的帖子。

5. 测试

该包包含一个用于 BaseModels 的基本测试类和一个更通用的 DbTestCase,它使用 DatabaseTransactions 保持测试数据库更改临时(更改在每个测试方法之后撤销)。

5.1. 模型测试(单元测试)

BaseModelTestCase 包含对所有模型属性/属性定义的默认值的测试,并检查其关系定义是否完整(相关模型中的对应定义)。

要使用为 BaseModel 提供的两个测试,可以扩展 BaseModelTestCase 类(需要实现抽象方法)。之后,新创建的测试类将可用 'testInverseRelationsForBulkActions' 和 'testDefaultValues' 两个测试。

<?php

namespace Tests\Unit\Models;

use Anexia\BaseModel\Tests\Unit\Models\BaseModelTestCase;
use App\User;

class PostTest extends BaseModelTestCase
{
    /**
     * @param int $userId
     */
    protected function mockLogin($userId = 1)
    {
        // mock the user of request()->user()
        $this->be($this->getUser($userId));
        $this->call('GET', 'login');
    }

    /**
     * @param int $id
     * @return User|null
     */
    public function getUser($id = 1)
    {
        return User::find($id);
    }
}

通过运行 phpunit 测试,将执行 BaseModelTestCase 中的测试,针对 Post 模型。

phpunit [--filter PostTest]

5.2. 控制器测试(功能测试)

RestControllerTestCase 提供了一个检查方法,用于检查第 分页 小节中描述的分页。此检查确保在列表响应中设置所有与分页相关的字段。

要使用此方法,可以扩展 RestControllerTestCase(需要实现抽象方法),并在(模拟的)GET 列表请求后包括分页检查。

<?php

namespace Tests\Feature\Controllers;

use Anexia\BaseModel\ExtendedModelParameters;
use Anexia\BaseModel\Tests\Feature\Controllers\RestControllerTestCase;
use App\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;

class PostControllerTest extends RestControllerTestCase
{
    /** string */
    const API_ROUTE = '/api/v1';

    /**
     * Test read list
     *
     * @return void
     */
    public function testReadPostList()
    {
        $response = $this->get(self::API_ROUTE . '/posts');

        $response->assertStatus(200);
        $body = json_decode($response->getContent(), true);

        $this->assertArrayHasKey('data', $body);
        $data = $body['data'];
        $this->assertInternalType('array', $data);

        $extendedParameters = new ExtendedModelParameters();
        $extendedParameters->setIncludes([
            // OR connected conditions
            [
                // AND connected conditions
                'therapist_id' => null,
                'unit_id' => null
            ],
            'therapist_id' => $this->currentUser->id,
            'unit.therapy.therapist_id' => $this->currentUser->id
        ]);

        $posts = Post::allExtended($extendedParameters);

        // add pagination checks
        $this->runPaginationTests($body, $posts->count());
    }
}

5.3. DbTestCase

Both BaseModelTestCase and RestControllerTestCase 使用 DbTestCase,这允许使用数据库事务。默认情况下,所有测试都使用定义为 'pgsql_testing' 的数据库连接。在每次运行的第一个测试中,数据库将从零开始创建(使用命令 'php artisan migrate' 和 'php artisan db:seed')。

6. 开发者列表

7. 项目相关的外部资源