czim / laravel-nestedupdater
Laravel嵌套关系数据的Eloquent模型更新器。
Requires
- php: ^8.1
Requires (Dev)
- mockery/mockery: ^1.4
- orchestra/database: ^7.0
- orchestra/testbench: ^7.0
- phpunit/phpunit: ^9.5
README
使用单个数据数组更新嵌套Eloquent模型关系的包。
此包可以通过单个方法调用轻松创建或更新一组嵌套的、相关的模型。例如,在更新Post模型时传入以下数据...
<?php $data = [ 'title' => 'updated title', 'comments' => [ 17, [ 'id' => 18, 'body' => 'updated comment body', 'author' => [ 'name' => 'John', ], ], [ 'body' => 'totally new comment', 'author' => 512, ], ], ];
...这将设置正在更新的Post模型的标题,但还额外
- 将评论#17链接到该帖子,
- 将评论#18链接和/或更新到该帖子,并设置评论的新正文,
- 创建一个名为'John'的新作者并链接到评论#18,
- 为该帖子创建一个新评论并将作者#512链接到它
支持任何组合的嵌套创建和更新;嵌套逻辑遵循Eloquent关系,并且可以高度自定义。
此外,此包提供了一次性验证嵌套关系数据的支持。
版本兼容性
变更日志
安装
通过Composer
$ composer require czim/laravel-nestedupdater
将以下代码行添加到位于您的config/app.php
文件中的提供者数组中
Czim\NestedModelUpdater\NestedModelUpdaterServiceProvider::class,
发布配置
$ php artisan vendor:publish
用法
请注意,此包在未设置至少一个允许嵌套更新的关系的配置之前不会执行任何嵌套更新。必须在使用之前设置配置。请参阅下面的配置部分。
NestedUpdatable 特性
通过使用NestedUpdatable
特性,可以轻松设置用于处理嵌套更新的模型。
<?php class YourModel extends Model { use \Czim\NestedModelUpdater\Traits\NestedUpdatable; // ...
传递到该模型create()
或update()
调用中的任何数据数组都将被处理为嵌套数据。请注意,fill()
(或模型上的任何其他数据相关方法)将不会受到影响,并且不要使用模型更新器处理嵌套数据。
如果您希望使用自己的ModelUpdaterInterface
实现,可以通过设置一个(受保护的)属性$modelUpdaterClass
来实现,该属性包含更新器的完全限定命名空间。这完全是可选的,仅用于灵活性。
<?php class YourCustomizedModel extends Model { use \Czim\NestedModelUpdater\Traits\NestedUpdatable; /** * You can refer to any class, as long as it implements the * \Czim\NestedModelUpdater\Contracts\ModelUpdaterInterface. * * @var class-string<\Czim\NestedModelUpdater\Contracts\ModelUpdaterInterface> */ protected $modelUpdaterClass = \Your\UpdaterClass\Here::class; /** * Additionally, optionally, you can set a class to be used * for the configuration, if you need to override how relation * configuration is determined. * * This class must implement * \Czim\NestedModelUpdater\Contracts\NestingConfigurationInterface * * @var class-string<\Czim\NestedModelUpdater\Contracts\NestingConfigurationInterface> */ protected $modelUpdaterConfigClass = \Your\UpdaterConfigClass::class;
手动使用ModelUpdater
或者,您可以通过创建一个实例手动使用ModelUpdater
。
<?php // Instantiate the modelupdater $updater = new \Czim\NestedModelUpdater\ModelUpdater(YourModel::class); // Or by using the service container binding (won't work in Laravel 5.4) $updater = app(\Czim\NestedModelUpdater\Contracts\ModelUpdaterInterface::class, [ YourModel::class ]); // Perform a nested data create operation $model = $updater->create([ 'some' => 'create', 'data' => 'here' ]); // Perform a nested data update on an existing model $updater->update([ 'some' => 'update', 'data' => 'here' ], $model);
配置
在nestedmodelupdater.php
配置中,在relations
键下根据模型配置您的关联。添加您希望允许嵌套更新的每个模型的完全限定命名空间键。在每个键下,添加您希望嵌套结构中每个关联数据具有的属性名称键。最后,对于这些键中的每一个,要么添加true
以启用具有所有默认设置的嵌套更新,要么在数组中覆盖设置。
作为一个简单的示例,如果您希望在创建帖子时添加评论,您的设置可能如下所示。
更新数据可能如下所示
<?php $data = [ 'title' => 'new post title', 'comments' => [ [ 'body' => 'new comment body text', 'author' => $existingAuthorId ], $existingCommentId ] ],
这可以用于更新帖子(或创建新帖子)以及创建新评论(链接到现有作者)和链接现有评论,并将两者都链接到帖子模型。
使此操作生效的relations
配置可能如下所示
<?php 'relations' => [ // The model class: App\Models\Post::class => [ // the data nested relation attribute // with a value of true to allow updates with default settings 'comments' => true ], App\Models\Comment::class => [ // this time, the defaults are overruled to only allow linking, // not direct updates of authors through nesting 'author' => [ 'link-only' => true ] ] ],
请注意,配置中不存在的关系将被忽略嵌套,并将作为填充数据传递到执行创建或更新操作的模型上。
更多关于关系配置的信息,请参阅这里。还可以查看配置文件以获取更多信息。
验证
模型更新器不会自动执行验证。本软件包提供嵌套验证作为一个独立的过程,可以像更新器本身一样自由实现。可以使用NestedValidator
类执行验证或根据提供的数据和关系配置返回验证规则。这将反映在更新时使用主键记录的更新或链接权限和规则。
在此处设置验证的更多信息,包括启动验证的不同方法。
非自增主键
处理具有非自增主键的模型的行为略有不同。通常,数据集中存在主键属性会使模型更新器假定需要链接或更新现有记录,如果找不到模型,则抛出异常。对于非自增键,假定任何不存在的键都应添加到数据库中。
如果您不希望这样,您将不得不在将数据传递给更新器之前过滤掉这些情况,或者创建自己的配置选项,使其成为可选设置。
临时ID:在嵌套数据中引用即将创建的模型
在通过嵌套更新创建模型时,可能需要创建单个新模型一次,但将其链接到多个其他父级。以下是一个数据示例
<?php $data = [ 'title' => 'New post title', 'comments' => [ [ 'body' => 'Some comment', 'author' => [ 'name' => 'Howard Hawks' ] ], [ 'body' => 'Another comment', 'author' => [ 'name' => 'Howard Hawks' ] ] ] ];
上述数据将创建两个具有相同名称的新作者,这可能是不可取的。如果只应创建一个新作者,并将其连接到两个评论,则可以使用临时ID来完成此操作
<?php $data = [ 'title' => 'New post title', 'comments' => [ [ 'body' => 'Some comment', 'author' => [ '_tmp_id' => 1, 'name' => 'Howard Hawks' ] ], [ 'body' => 'Another comment', 'author' => [ '_tmp_id' => 1, ] ] ] ];
这将创建一个具有给定名称的单个新作者,并将其连接到两个评论。
_tmp_id
引用必须对即将创建的模型是唯一的。它可以是一个整数或字符串值,但不能包含点(.
)。
由于检查临时ID存在一定的性能开销,因此默认禁用。要启用它,只需在配置中将allow-temporary-ids
设置为true
。
对于多个互相关联的临时ID创建操作,没有深度检查循环引用或(可能性极小的)依赖性问题,因此在使用此功能时要小心,或在使用前手动进行深度验证。
不受保护的属性
默认情况下,更新和创建遵循相关模型的fillable
保护。
有两种方法可以绕过这一点。
强制填充属性
可以让模型更新器完全忽略填充保护。
您可以通过在调用update()
或create()
之前在更新器上调用force()
,或者直接调用forceUpdate()
或forceCreate()
来实现。
<?php // Instantiate the modelupdater $updater = new \Czim\NestedModelUpdater\ModelUpdater(YourModel::class); // Perform a nested data create operation, disregarding any fillable guard $model = $updater->forceCreate([ 'user_id' => 1, 'some' => 'create', 'data' => 'here' ]);
设置顶级模型属性的具体值
可以通过将需要设置的值单独传递到顶级模型,准备模型更新器以设置绕过特定模型属性保护的属性。这可以通过在调用update()
或create()
之前在模型更新器上使用setUnguardedAttribute()
方法来实现。
这允许在不更改主要数据树的情况下设置一些特定值。
示例
<?php // Instantiate the modelupdater $updater = new \Czim\NestedModelUpdater\ModelUpdater(YourModel::class); // Queue an non-fillable attribute to be stored on the create model $updater->setUnguardedAttribute('user_id', 1); // Perform a nested data create operation $model = $updater->create([ 'some' => 'create', 'data' => 'here' ]);
在这种情况下,user_id
将直接存储在新创建的模型上。
作为一个安全措施,在成功的模型更新或创建操作后,任何先前设置的未受保护属性将自动清除。对于要执行的每个后续更新/创建,再次设置它们。
也可以一次性设置整个未受保护的属性数组
<?php $updater->setUnguardedAttributes([ 'some_attribute' => 'example', 'another' => 'value', ]);
当前排队等待分配的未受保护属性可以使用 getUnguardedAttributes()
获取。未受保护的属性也可以在任何时候使用 clearUnguardedAttributes()
清除。
关联数组数据
注意在提交复数关系条目的关联数组时。虽然支持这种做法,但很容易导致验证失败:当使用包含 '.'
的关联键提交数据时会出现问题
<?php $data = [ 'comments' => [ 'some.key' => [ 'body' => 'Some comment', ], 'another-key' => [ 'body' => 'Another comment', ] ] ];
由于 Laravel 的 Arr::get()
和其他基于点符号的查找方法被使用,嵌套验证将无法正确验证具有此类键的数据条目。
请注意,此类关联键对模型更新器本身没有作用。最好的避免问题的方法是规范化您的数据,确保所有复数关系数组都是非关联的。或者,可以将数组键中的任何 .
替换为占位符。有关提示,请参见下方的“扩展功能”。
扩展功能
ModelUpdater
类应被视为定制的首选候选。可以覆盖 normalizeData()
方法来操纵在解析之前传入的数据数组。此外,还可以查看 deleteFormerlyRelatedModel()
,它可能在需要细化删除条件的情况下很有用。
请注意,可以通过使用 updater
属性为特定关系设置自己的模型更新器扩展。
验证器与更新器的结构非常相似,因此同样容易扩展。
贡献
有关详细信息,请参阅 CONTRIBUTING。
鸣谢
许可
MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件。