Package Data | |
---|---|
Maintainer Username: | czim |
Maintainer Contact: | coen.zimmerman@endeavour.nl (Coen Zimmerman) |
Package Create Date: | 2016-06-26 |
Package Last Update: | 2022-11-08 |
Language: | PHP |
License: | MIT |
Last Refreshed: | 2024-12-11 15:14:56 |
Package Statistics | |
---|---|
Total Downloads: | 46,314 |
Monthly Downloads: | 538 |
Daily Downloads: | 21 |
Total Stars: | 19 |
Total Watchers: | 4 |
Total Forks: | 4 |
Total Open Issues: | 2 |
Package for updating nested eloquent model relations using a single data array.
This package will make it easy to create or update a group of nested, related models through a single method call. For example, when passing in the following data for an update of a Post model ...
<?php
$data = [
'title' => 'updated title',
'comments' => [
17,
[
'id' => 18,
'body' => 'updated comment body',
'author' => [
'name' => 'John',
],
],
[
'body' => 'totally new comment',
'author' => 512,
],
],
];
... this would set a new title for Post model being updated, but additionally:
Any combination of nested creates and updates is supported; the nesting logic follows that of Eloquent relationships and is highly customizable.
Additionally, this package provides support for validating data with nested relations all at once.
Laravel | Package :----------------|:-------- 5.3 and lower | 1.0 5.4 to 5.6 | 1.4 5.7 and up | 1.5
Via Composer
$ composer require czim/laravel-nestedupdater
Add this line of code to the providers array located in your config/app.php
file:
Czim\NestedModelUpdater\NestedModelUpdaterServiceProvider::class,
Publish the configuration:
$ php artisan vendor:publish
Note that this package will not do any nested updates without setting up at least a configuration for the relations that you want to allow nested updates for. Configuration must be set before this can be used at all. See the configuration section below.
An easy way to set up a model for processing nested updates is by using the NestedUpdatable
trait:
<?php
class YourModel extends Model
{
use \Czim\NestedModelUpdater\Traits\NestedUpdatable;
// ...
Any data array passed into create()
or update()
calls for that model will be processed for nested data.
Note that fill()
(or any other data-related methods on the model) will not be affected, and do not process nested data with the model updater.
If you wish to use your own implementation of the ModelUpdaterInterface
, you may do so by setting a (protected) property $modelUpdaterClass
with the fully qualitied namespace for the updater.
This is entirely optional and merely availble for flexibility.
<?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 string
*/
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 string
*/
protected $modelUpdaterConfigClass = \Your\UpdaterConfigClass::class;
Alternatively, you can use the ModelUpdater
manually, by creating an instance.
<?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);
In the nestedmodelupdater.php
config, configure your relations per model under the relations
key.
Add keys of the fully qualified namespace of each model that you want to allow nested updates for.
Under each, add keys for the attribute names that you want your nested structure to have for each relation's data.
Finally, for each of those, either add true
to enable nested updates with all default settings, or override settings in an array.
As a simple example, if you wish to add comments when creating a post, your setup might look like the following.
The updating data would be something like this:
<?php
$data = [
'title' => 'new post title',
'comments' => [
[
'body' => 'new comment body text',
'author' => $existingAuthorId
],
$existingCommentId
]
],
This could be used to update a post (or create a new post) with a title, create a new comment (which is linked to an existing author) and link an existing comment, and link both to the post model.
The relations
-configuration to make this work would look like this:
<?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
]
]
],
Note that any relation not present in the config will be ignored for nesting, and passed as fill data into the main model on which the create or update action is performed.
More information on relation configuration. Also check out the configuration file for further notes.
Validation is not automatically performed by the model updater. This package offers nested validation as a separate process,
that may be implemented as freely as that of the updater itself.
A NestedValidator
class may be used to perform validation or return validation rules based on the data provided and the
relations configuration set. This will reflect update- or link-only rights and rules for records existing on using primary
keys when updating.
Further information on setting up validation here, including different approaches for intiating validation.
The behavior for dealing with models that have non-incrementing primary keys is slightly different. Normally, the presence of a primary key attribute in a data set will make the model updater assume that an existing record needs to be linked or updated, and it will throw an exception if it cannot find the model. Instead, for non-incrementing keys, it is assumed that any key that does not already exist is to be added to the database.
If you do not want this, you will have to filter out these occurrences before passing in data to the updater, or make your own configuration option to make this an optional setting.
When creating models through nested updates, it may be necessary to create a single new model once, but link it to multiple other parents. Take the following data example:
<?php
$data = [
'title' => 'New post title',
'comments' => [
[
'body' => 'Some comment',
'author' => [
'name' => 'Howard Hawks'
]
],
[
'body' => 'Another comment',
'author' => [
'name' => 'Howard Hawks'
]
]
]
];
The above data would create two new authors with the same name, which is likely undesirable. If only one new author should be created, and connected to both comments, this may be done using temporary IDs:
<?php
$data = [
'title' => 'New post title',
'comments' => [
[
'body' => 'Some comment',
'author' => [
'_tmp_id' => 1,
'name' => 'Howard Hawks'
]
],
[
'body' => 'Another comment',
'author' => [
'_tmp_id' => 1,
]
]
]
];
This would create a single new author with the given name and connect it to both comments.
The _tmp_id
reference must be unique for one to-be created model.
It may be an integer or a string value, but it must not contain a period (.
).
Because there is a (minor) performance cost to checking for temporary IDs, this is disabled by default.
To enable it, simply set allow-temporary-ids
to true
in the configuration.
There are no deep checks for cyclical references or (fairly unlikely) dependency issues for multiple interrelated temporary ID create operations, so be careful with this or perform in-depth validation manually beforehand.
Updates and creates adhere to the fillable
guards for the relevant models.
It is possible to prepare the model updater to set attributes bypassing the guard.
This may be done using the setUnguardedAttribute()
method on the model updater, before calling update()
or create()
.
Example:
<?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', \Auth::user()->id);
// Perform a nested data create operation
$model = $updater->create([ 'some' => 'create', 'data' => 'here' ]);
In this case the user_id
would be stored directly on the newly created model.
As a safety measure, any previously set unguarded attributes will be cleared automatically after a succesful model update or create operation. Set them again for each subsequent update/create to be performed.
It is also possible to set an entire array of unguarded attributes to assign at once:
<?php
$updater->setUnguardedAttributes([
'some_attribute' => 'example',
'another' => 'value',
]);
Currently queued unguarded attributes to be assigned may be retrieved using getUnguardedAttributes()
.
The unguarded attributes may also be cleared at any time using clearUnguardedAttributes()
.
Be careful submitting associative arrays for entries of plural relations.
They are supported, but can easily break validation:
Problems will arise when data is submitted with associative keys that contain '.'
like so:
<?php
$data = [
'comments' => [
'some.key' => [
'body' => 'Some comment',
],
'another-key' => [
'body' => 'Another comment',
]
]
];
Since Laravel's Arr::get()
and other dot-notation based lookup methods are used,
nested validation will fail to properly validate data entries with such keys.
Note that such associative keys serve no purpose for the model updater itself.
The best way to avoid problems is to normalize your data so that all plural relation arrays are non-associative.
Alternatively, replace any .
in the array's keys with a placeholder.
See 'Extending functionality' below for tips.
The ModelUpdater
class should be considered a prime candidate for customization.
The normalizeData()
method may be overridden to manipulate the data array passed in before it is parsed.
Additionally check out deleteFormerlyRelatedModel()
, which may be useful to set up in cases where conditions for deleting need to be refined.
Note that it is your own ModelUpdater extension may be set for specific relations by using the updater
attribute.
The validator shares much of the updater's structure, so it should be equally easy to extend.
Please see CONTRIBUTING for details.
The MIT License (MIT). Please see License File for more information.