use*_*172 21 php crud one-to-many laravel eloquent
如果我有多对多的关系,那么用它的sync
方法更新关系是非常容易的.
但是我会用什么来同步一对多的关系呢?
posts
:id, name
links
:id, name, post_id
在这里,每个Post
可以有多个Link
s.
我想将与数据库中特定帖子相关联的链接与输入的链接集合(例如,从我可以添加,删除和修改链接的CRUD表单)同步.
应删除数据库中不存在于我的输入集合中的链接.应更新数据库和输入中存在的链接以反映输入,并且仅在我的输入中存在的链接应作为新记录添加到数据库中.
总结所需的行为:
luk*_*ter 15
不幸的是,没有sync
一对多关系的方法.自己做这件事非常简单.至少如果您没有任何外键引用links
.因为那时你可以简单地删除行并再次插入它们.
$links = array(
new Link(),
new Link()
);
$post->links()->delete();
$post->links()->saveMany($links);
Run Code Online (Sandbox Code Playgroud)
如果您确实需要更新现有的(无论出于何种原因),您需要完全按照您在问题中描述的内容进行操作.
您可以使用UPSERT插入或更新重复键,也可以使用关系。
这意味着您可以将旧数据与新数据进行比较,并使用一个数组,其中包含要更新的数据和要插入到同一查询中的数据。
您也可以删除其他不需要的 id。
这里有一个例子:
$toSave = [
[
'id'=>57,
'link'=>'...',
'input'=>'...',
],[
'id'=>58,
'link'=>'...',
'input'=>'...',
],[
'id'=>null,
'link'=>'...',
'input'=>'...',
],
];
// Id of models you wish to keep
// Keep existing that dont need update
// And existing that will be updated
// The query will remove the rest from the related Post
$toKeep = [56,57,58];
// We skip id 56 cause its equal to existing
// We will insert or update the rest
// Elements in $toSave without Id will be created into the relationship
$this->$relation()->whereNotIn('id',$toKeep)->delete();
$this->$relation()->upsert(
$toSave, // Data to be created or updated
['id'], // Unique Id Column Key
['link','input'] // Columns to be updated in case of duplicate key, insert otherwise
);
Run Code Online (Sandbox Code Playgroud)
这将创建下一个查询:
delete from
`links`
where
`links`.`post_id` = 247
and `links`.`post_id` is not null
and `id` not in (56, 57, 58)
Run Code Online (Sandbox Code Playgroud)
和:
insert into
`links` (`id`, `link`, `input`)
values
(57, '...', '...'),
(58, '...', '...'),
(null, '...', '...')
on duplicate key update
`link` = values(`link`),
`input` = values(`input`)
Run Code Online (Sandbox Code Playgroud)
通过这种方式,您只需 2 个查询即可更新关系的所有元素。例如,如果您有 1,000 个帖子,并且您想要更新所有帖子的所有链接。
删除和读取相关实体的问题在于,它将破坏您可能对这些子实体具有的任何外键约束。
更好的解决方案是修改Laravel的HasMany
关系以包含以下sync
方法:
<?php
namespace App\Model\Relations;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/HasMany.php
*/
class HasManySyncable extends HasMany
{
public function sync($data, $deleting = true)
{
$changes = [
'created' => [], 'deleted' => [], 'updated' => [],
];
$relatedKeyName = $this->related->getKeyName();
// First we need to attach any of the associated models that are not currently
// in the child entity table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->newQuery()->pluck(
$relatedKeyName
)->all();
// Separate the submitted data into "update" and "new"
$updateRows = [];
$newRows = [];
foreach ($data as $row) {
// We determine "updateable" rows as those whose $relatedKeyName (usually 'id') is set, not empty, and
// match a related row in the database.
if (isset($row[$relatedKeyName]) && !empty($row[$relatedKeyName]) && in_array($row[$relatedKeyName], $current)) {
$id = $row[$relatedKeyName];
$updateRows[$id] = $row;
} else {
$newRows[] = $row;
}
}
// Next, we'll determine the rows in the database that aren't in the "update" list.
// These rows will be scheduled for deletion. Again, we determine based on the relatedKeyName (typically 'id').
$updateIds = array_keys($updateRows);
$deleteIds = [];
foreach ($current as $currentId) {
if (!in_array($currentId, $updateIds)) {
$deleteIds[] = $currentId;
}
}
// Delete any non-matching rows
if ($deleting && count($deleteIds) > 0) {
$this->getRelated()->destroy($deleteIds);
$changes['deleted'] = $this->castKeys($deleteIds);
}
// Update the updatable rows
foreach ($updateRows as $id => $row) {
$this->getRelated()->where($relatedKeyName, $id)
->update($row);
}
$changes['updated'] = $this->castKeys($updateIds);
// Insert the new rows
$newIds = [];
foreach ($newRows as $row) {
$newModel = $this->create($row);
$newIds[] = $newModel->$relatedKeyName;
}
$changes['created'][] = $this->castKeys($newIds);
return $changes;
}
/**
* Cast the given keys to integers if they are numeric and string otherwise.
*
* @param array $keys
* @return array
*/
protected function castKeys(array $keys)
{
return (array) array_map(function ($v) {
return $this->castKey($v);
}, $keys);
}
/**
* Cast the given key to an integer if it is numeric.
*
* @param mixed $key
* @return mixed
*/
protected function castKey($key)
{
return is_numeric($key) ? (int) $key : (string) $key;
}
}
Run Code Online (Sandbox Code Playgroud)
您可以重写Eloquent的Model
类HasManySyncable
而不是标准HasMany
关系来使用:
<?php
namespace App\Model;
use App\Model\Relations\HasManySyncable;
use Illuminate\Database\Eloquent\Model;
abstract class MyBaseModel extends Model
{
/**
* Overrides the default Eloquent hasMany relationship to return a HasManySyncable.
*
* {@inheritDoc}
* @return \App\Model\Relations\HasManySyncable
*/
public function hasMany($related, $foreignKey = null, $localKey = null)
{
$instance = $this->newRelatedInstance($related);
$foreignKey = $foreignKey ?: $this->getForeignKey();
$localKey = $localKey ?: $this->getKeyName();
return new HasManySyncable(
$instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
);
}
Run Code Online (Sandbox Code Playgroud)
假设您的Post
模型扩展MyBaseModel
并具有links()
hasMany
关系,则可以执行以下操作:
$post->links()->sync([
[
'id' => 21,
'name' => "LinkedIn profile"
],
[
'id' => null,
'label' => "Personal website"
]
]);
Run Code Online (Sandbox Code Playgroud)
此多维数组中具有id
与子实体表(links
)相匹配的所有记录都将被更新。表中该数组中不存在的记录将被删除。表中不存在的数组中的记录(具有不匹配的id
,或者id
为null)将被视为“新”记录,并将被插入数据库中。