illuminatech/sync-many-attribute

允许通过数组属性控制 Eloquent 多对多关系

1.1.5 2024-03-25 11:09 UTC

This package is auto-updated.

Last update: 2024-08-25 12:01:02 UTC


README

通过数组属性同步 Eloquent 多对多关系


此扩展允许通过数组属性控制 Eloquent 多对多关系。

有关许可证信息,请检查LICENSE文件。

Latest Stable Version Total Downloads Build Status

安装

安装此扩展的首选方式是通过 composer

运行

php composer.phar require --prefer-dist illuminatech/sync-many-attribute

或将其添加到 composer.json 的 require 部分。

"illuminatech/sync-many-attribute": "*"

用法

此扩展允许通过数组属性控制 Eloquent 多对多关系。每个这样的属性匹配特定的 BelongsToMany 关系并接受相关模型 ID 的数组。关系将在模型保存期间自动同步。

注意:通常这种方法意义不大,因为 Eloquent 已经为多对多关系同步提供了流畅的接口。然而,当与第三方 CMS(如 Nova)一起使用时,这可能很有用,在 Nova 中,你对模型保存和后处理的控制较少。此外,它还可以简化控制器代码,通过常规属性大量赋值来删除关系操作。

为了使用此功能,您应将 \Illuminatech\SyncManyAttribute\SyncManyToManyAttribute 特性添加到您的模型类中,并声明 syncManyToManyAttributes() 方法,定义用于关系同步的属性。此方法应返回一个数组,其中每个键是新虚拟属性名,值是要同步的关系名。

例如

<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminatech\SyncManyAttribute\SyncManyToManyAttribute;

/**
 * @property int[] $category_ids
 * @property int[] $tag_ids
 */
class Item extends Model
{
    use SyncManyToManyAttribute;

    protected function syncManyToManyAttributes(): array
    {
        return [
            'category_ids' => 'categories',
            'tag_ids' => 'tags',
        ];
    }

    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withPivot(['created_at']);
    }

    // ...
}

用法示例

<?php

$item = new Item();
$item->category_ids = Category::query()->pluck('id')->toArray();
// ...
$item->save(); // relation `Item::categories()` synchronized automatically

$item = $item->fresh();
var_dump($item->category_ids); // outputs array of category IDs like `[1, 3, 8, ...]`

您可以在 HTML 表单输入组合期间使用同步属性。例如

...
<select multiple="multiple" name="category_ids[]" id="category_ids">
@foreach ($allCategories as $category)
    <option value="{{ $category->id }}" @if(in_array($category->id, $item->category_ids)) selected="selected" @endif>{{ $category->name }}</option>
@endforeach
...
</select>

控制器代码示例

<?php

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class KioskController extends Controller
{
    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'name' => ['required', 'string'],
            // ...
            'category_ids' => ['required', 'array'],
            'category_ids.*' => ['int', 'exists:categories,id'],
            'tag_ids' => ['required', 'array'],
            'tag_ids.*' => ['int', 'exists:tags,id'],
        ]);
        
        $item = new Item;
        $item->fill($validatedData); // single assignment covers all many-to-many relations
        $item->save(); // relation `Item::categories()` synchronized automatically
        
        // return response
    }
}

注意:请记住,您需要将多对多同步属性的名称添加到 \Illuminate\Database\Eloquent\Model::$fillable,以便它们可用于大量赋值。

设置枢轴属性

您可以设置在每次关系同步期间应保存的枢轴属性。为此,您应将同步属性定义为数组,其中键定义关系名称,值定义枢轴属性。可以使用 \Closure 定义特定的枢轴属性值或整个枢轴属性集。例如

<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminatech\SyncManyAttribute\SyncManyToManyAttribute;

class Item extends Model
{
    use SyncManyToManyAttribute;

    protected function syncManyToManyAttributes(): array
    {
        return [
            'category_ids' => [
                'categories' => [
                    'type' => 'help-content',
                ],
            ],
            'tag_ids' => [
                'tags' => [
                    'attached_at' => function (Item $model) {
                        return now();
                    }
                ],
            ],
        ];
    }

    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class)->withPivot(['type']);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withPivot(['attached_at']);
    }

    // ...
}

您可以使用 \Illuminatech\SyncManyAttribute\ManyToManyAttribute 以更面向对象的方式创建同步属性定义

<?php

use Illuminate\Database\Eloquent\Model;
use Illuminatech\SyncManyAttribute\ManyToManyAttribute;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminatech\SyncManyAttribute\SyncManyToManyAttribute;

class Item extends Model
{
    use SyncManyToManyAttribute;

    protected function syncManyToManyAttributes(): array
    {
        return [
            'category_ids' => (new ManyToManyAttribute)
                ->relationName('categories')
                ->pivotAttributes(['type' => 'help-content']),
            'tag_ids' => (new ManyToManyAttribute)
                ->relationName('tags')
                ->pivotAttributes([
                    'attached_at' => function (Item $model) {
                        return now();
                    },
                ]),
        ];
    }

    public function categories(): BelongsToMany
    {
        return $this->belongsToMany(Category::class)->withPivot(['type']);
    }
    
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withPivot(['attached_at']);
    }

    // ...
}

定义的枢轴属性将在模型保存时自动同步关系期间保存

<?php

$item = new Item();
$item->category_ids = Category::query()->pluck('id')->toArray();
// ...
$item->save(); // relation `Item::categories()` synchronized automatically

$category = $item->categories()->first();
var_dump($category->pivot->type); // outputs 'help-content'

Nova 集成

此扩展的主要优势之一是支持第三方 CMS(如 Nova)。您可以使用同步属性,允许用户直接从创建/更新表单设置多对多关系,而不是从详细信息页面单独操作列表。

您可以为 BelongsToMany 关系创建多选框或复选框列表的输入。像 fourstacks/nova-checkboxes 这样的包可用于此类字段。最终的 Nova 资源可能如下所示

<?php

use Laravel\Nova\Resource;
use Laravel\Nova\Fields\ID;
use Fourstacks\NovaCheckboxes\Checkboxes;

class Item extends Resource
{
    public static $model = \App\Models\Item::class; // uses `SyncManyToManyAttribute` for 'categories'
    
    public function fields(Request $request)
        {
            return [
                ID::make()->sortable(),
    
                // ...
    
                // use single checkbox list input instead of `\Laravel\Nova\Fields\BelongsToMany`:
                Checkboxes::make(__('Categories'), 'category_ids')
                    ->options(\App\Models\Category::pluck('name', 'id')),
            ];
        }
}