bayareawebpro/searchable-resource

此包已被废弃且不再维护。未建议替代包。

Laravel 可搜索资源构建器

v1.0.29 2022-02-09 00:21 UTC

README

CI Coverage Downloads Version MIT

Searchable Resource Builder 是在 Laravel 应用程序中构建可搜索资源响应的抽象。将查询逻辑提取为可重用的块,同时使用流畅的构建器接口处理可搜索/可过滤/可排序请求和 JSON/API 资源。

composer require bayareawebpro/searchable-resource

基本用法

SearchableResources 实现了 Responsable 接口,这使得它们可以从控制器中轻松返回。它也可以与 blade 一起使用。

make 方法接受 Eloquent Builder 的实例。

SearchableResource::make(User::query());

排序和排序

您可以指定任意多的可排序列。

SearchableResource::make(User::query())
	->orderable(['name', 'email'])
	->orderBy('name')
	->sort('desc')
	->paginate(16);

默认设置

  • 按 ID 排序
  • 降序排序

完整示例

use App\User;
use App\Queries\UserSearch;
use App\Queries\RoleFilter;
use App\Http\Resources\UserResource;
use BayAreaWebPro\SearchableResource\SearchableResource;
use BayAreaWebPro\SearchableResource\SearchableBuilder;

SearchableResource::make(User::query())
    ->resource(UserResource::class)
    ->queries([
        UserSearch::class,
        RoleFilter::class
    ])
    ->orderable([
        'id', 'name', 'email', 'role',
        'created_at', 'updated_at',
    ])
    ->appendable([
        'created_for_humans',
        'updated_for_humans',
    ])
    ->select([
        'id',
        'name', 'email', 'role',
        'created_at', 'updated_at',
    ])
    ->rules([
       'my_filter_key' => 'required|string'
    ])
    ->params([
       'my_filter_key' => 'my_default'
    ])
    ->options([
       'my_filter_key' => ['my_default', 'option2', 'option3']
    ])
    ->with([
        'my_key' => true
    ])
    ->when(true, fn(SearchableBuilder $builder)=>$builder
        ->with([
            'my_key' => false
        ])
    )
    ->orderBy('updated_at')
    ->sort('desc')
    ->paginate(16)
    ->labeled();

Blade/视图示例

执行查询并返回包含项目和选项的视图。

public function index()
{
    $resource = SearchableResource::make(User::query())
        ->query(Users::make())
        ->orderable(['name', 'email'])
        ->orderBy('name')
        ->sort('desc')
        ->paginate(5)
        ->execute()
    ;
    return view('users.index', [
        'items' =>$resource->getItems(),
        'search' =>$resource->getSearch(),
        'order_by' =>$resource->getOrderBy(),
        'per_page' =>$resource->getPerPage(),
        'options' =>$resource->getOptions(),
        'sort' =>$resource->getSort(),
    ]);
}
<x-admin::form method="GET">
    <x-admin::search
        name="search"
        value="{{ $search ?? null }}"
    />
    <x-admin::select
        name="order_by"
        value="{{ $order_by ?? null }}"
        :options="$options->get('order_by')"
    />
    <x-admin::select
        name="sort"
        value="{{ $sort ?? null }}"
        :options="$options->get('sort')"
    />
    <x-admin::select
        name="per_page"
        value="{{ $per_page ?? null }}"
        :options="$options->get('per_page')"
    />
    <x-admin::submit
        label="{{ __('resources.filter') }}"
    />
</x-admin::form>

JSON 资源

SearchableResources 默认为通用 JsonResources。您可以在构建响应时轻松指定应使用哪个资源类来映射您的模型。

必须扩展 JsonResource

SearchableResource::make(User::query())->resource(UserResource::class);

可调用的查询

查询以可调用的类形式表达,这些类扩展了包含每个请求字段逻辑的 AbstractQuery 类。查询可以应用于多个属性/列,以及多个 orWhere 子句的输入。

php artisan make:searchable NameQuery

以下是一个通用名称查询的示例

<?php declare(strict_types=1);
 
namespace App\Queries;
 
use Illuminate\Database\Eloquent\Builder;
use BayAreaWebPro\SearchableResource\AbstractQuery;
 
class LikeQuery extends AbstractQuery
{
    public string $field = 'search';
    protected string $attribute = 'name';
    public function __invoke(Builder $builder): void
    {
        $builder->where($this->attribute, "like", "%{$this->getValue($this->field)}%");
    }
}
SearchableResource::make(User::query())
    ->query(
        LikeQuery::make()
            ->field('search')
            ->attribute('last_name')
    )
    ->query(
        SelectQuery::make()
            ->field('role')
            ->attribute('role')
            ->options(['admin', 'customer'])
     )
;

条件查询协议

实现 ConditionalQuery 协议的查询只有在它们的 applies 方法返回 true 时才会应用。

默认情况下,使用 ConditionalQuery 协议扩展 AbstractQuery 类的查询已经为您实现了此方法,通过在请求上调用 filled 方法。
覆盖父方法以自定义。

<?php declare(strict_types=1);

namespace App\Queries;
 
use BayAreaWebPro\SearchableResource\AbstractQuery;
use BayAreaWebPro\SearchableResource\Contracts\ConditionalQuery;
 
class ConditionalRoleQuery extends AbstractQuery implements ConditionalQuery
{
    public string $field = 'role';
    protected string $attribute = 'role';
 
    public function __invoke(Builder $builder): void
    {
        $builder->where($this->attribute, $this->getValue($this->field));
    }

    public function getApplies(): bool
    {
    	return parent::getApplies(); // Customize with $this->request
    }
}

验证

查询可以通过实现 ValidatableQuery 协议来指定自己的验证规则,以便与可搜索集合合并规则。

ValidatableQuery 协议

实现 ValidatableQuery 协议的查询将它们的返回规则合并到验证器中,否则将忽略规则。

<?php declare(strict_types=1);

namespace App\Queries;
 
use BayAreaWebPro\SearchableResource\AbstractQuery;
use BayAreaWebPro\SearchableResource\Contracts\ValidatableQuery;
 
class ConditionalRoleQuery extends AbstractQuery implements ValidatableQuery
{

    public string $role = 'role';
    public string $admins = 'only_admins';
    
    protected string $attribute = 'role';
 
    public function __invoke(Builder $builder): void
    {
        $builder->where($this->attribute, $this->getValue($this->admins) ?: $this->getValue($this->role));
    }

    public function getRules(): array
    {
        return [
           $this->role => [
               'required'
           ],
           $this->admins => [
               'sometimes'
           ],
        ];
    }
}

ProvidesOptions 协议

查询可以通过实现 ProvidesOptions 协议来提供选项,这些选项将通过实现 ProvidesOptions 协议添加到请求选项数据中。此方法应返回一个平面数组,该数组将注入到响应查询选项数据中。

<?php declare(strict_types=1);

namespace App\Queries;
 
use BayAreaWebPro\SearchableResource\AbstractQuery;
use BayAreaWebPro\SearchableResource\Contracts\ProvidesOptions;
 
class ProvidesOptionsQuery extends AbstractQuery implements ProvidesOptions
{

    public string $field = 'role';
    protected string $attribute = 'role';
 
    public function __invoke(Builder $builder): void
    {
        $builder->where($this->attribute, $this->getValue($this->field));
    }

    public function getOptions(): array
    {
        return [
            $this->field => [
                'admin', 'editor'
            ],
        ];
    }
}

选项格式化

选项可以通过在构建器上调用 labeled() 方法进行格式化,以便与表单和过滤器一起使用。labeled 方法接受一个布尔值,可以用来在请求有会话时启用。

您可以从查询中返回预格式化的选项(标签/值数组)或使用格式化器生成带标签的选项。

API 选项模式

API 请求的选项通常未格式化以提高速度。

public function getOptions(): array
{
    return [
        'role' => [
            'admin',
            'customer'
        ]
    ];
}

Blade 选项模式

Blade 请求的选项可以格式化以提高可用性。

public function getOptions(): array
{
    return [
        $this->field => [
            [
                'label' => 'Admin',
                'value' => 'admin'
            ],
            [
                'label' => 'Customer',
                'value' => 'customer'
            ]
        ]
    ];
}

FormatsOptions 协议

您可以通过指定格式化器实例来覆盖默认格式化器。

SearchableResource::make(User::query())->useFormatter(new OptionsFormatter);
<?php declare(strict_types=1);

namespace App\Http\Resources\Formatters;

use Illuminate\Support\Collection;
use BayAreaWebPro\SearchableResource\OptionsFormatter as Formatter;

class OptionsFormatter extends Formatter {

    /**
     * @param string $key
     * @param Collection $options
     * @return Collection
     */
    public function __invoke(string $key, Collection $options): Collection
    {
        if($key === 'abilities'){
            return $this->nullable($this->literal($options));
        }
        if($key === 'role'){
            return $this->nullable($this->titleCase($options));
        }
        return $this->baseOptions($key, $options);
    }
}

设置默认选项

您可以在服务提供器中设置一个解析回调,以预先绑定选项到每个实例。

use BayAreaWebPro\SearchableResource\OptionsFormatter;
use BayAreaWebPro\SearchableResource\SearchableBuilder;

$this->app->resolving(
    SearchableBuilder::class,
    function (SearchableBuilder $builder){
    return $builder
        ->useFormatter(new OptionsFormatter)
        ->labeled(request()->hasSession())
        ->orderBy('created_at')
        ->paginate(8)
        ->sort('desc')
    ;
});

添加查询

查询可以通过两种方式添加。首先是通过引用类字符串以便于批量使用。

use App\Queries\RoleQuery;

SearchableResource::make(User::query())
	->queries([
		RoleQuery::class
	]);

通过使用 make 方法实例化每个查询。当您需要更多方法和逻辑来确定使用时,这可能很有用。

use App\Queries\RoleQuery;

SearchableResource::make(User::query())
	->query(RoleQuery::make());

可追加数据

可以使用以下方法将属性和字段追加到响应中

对于模型属性

SearchableResource::make(User::query())
    ->appendable([
        'created_for_humans',
        'updated_for_humans',
        'bytes_for_humans',
    ]);

对于附加数据(追加到响应中)

SearchableResource::make(User::query())
    ->with([
        'my_key' => []
    ]);
{
    "my_key": [],
    "data": []
}

对于请求字段(追加到响应中的查询)

SearchableResource::make(User::query())
    ->fields([
        'my_filter_state'
    ]);
{
    "query": {
        "my_filter_state": true
    }
}

条件回调

您可以使用回调或可调用类来获得更少的链式方法并实现更多控制。

class SessionEnabledQuery{
    public function __invoke(SearchableBuilder $builder): void 
    {
        $builder->labeled();
    }
}

SearchableResource::make(User::query())
    ->when(request()->hasSession(), new SessionEnabledQuery)
    ->when(request()->hasSession(), function(SearchableBuilder $builder){
        $builder->labeled();
    })
;

触摸回调

通过可调用类配置构建器很有用。

use BayAreaWebPro\SearchableResource\SearchableBuilder;
use BayAreaWebPro\SearchableResource\Contracts\InvokableBuilder;
class UserSearchable implements InvokableBuilder{
    public function __invoke(SearchableBuilder $builder): void 
    {
        $builder->queries([
            RoleQuery::class
        ]);
    }
}

SearchableResource::make(User::query())->tap(new UserSearchable);

响应输出

为了方便起见,将相关的查询参数和请求选项追加到输出中。在分页参数中添加了两个附加属性,以消除客户端/用户侧的条件判断,即 isFirstPageisLastPage,这使得通过属性(Vue | React)轻松禁用分页按钮。

注意:如果不使用 pagination 方法,则所有与分页相关的属性都将从输出数据中过滤掉。

"data": [
    //
],
"pagination": {
    "isFirstPage": true,
    "isLastPage": true,
    ...default pagination props...
},
"query": {
    "page": 1,
    "sort": "desc",
    "order_by": "id",	
    "search": "term",
    "per_page": 4,	
},
"options": {
    "orderable": [
        "id", 
        "name"
    ],
    "sort": [
        "asc"
        "desc"
    ]
}

测试

composer test
composer lint