Kez
1
I have a CakePHP 5 app, with two tables - Titles
and Series
.
Series
hasMany Titles
,
Titles
table has a series_id
property
Titles
also have series_number
property
Imagine I have three titles, The Fellowship of the Ring, The Two Towers and The Return of the King. The titles are already in the DB, and I want to make a new Series. So I send some data to the endpoint like this:
{
"name": "The Lord of the Rings",
"titles": [
{ "id": 1, "series_number": 1 },
{ "id": 2, "series_number": 2 },
{ "id": 3, "series_number": 3 }
]
}
I want the new Series to update the existing titles, with the new series_id
and the series_number
property. I can’t just use _ids
for this cause I have additional data to save.
My save endpoint looks like this:
/**
* Add method
*
* @return void
* @throws \Cake\Http\Exception\InternalErrorException
*/
public function add(): void
{
$this->request->allowMethod(['post']);
$series = $this->Series->newEntity($this->request->getData(), ['associated' => ['Titles']]);
if ($this->Series->save($series)) {
$series = $this->Series->get($series->id, contain: ['Titles']);
} else {
throw new InternalErrorException('Could not save the series. Please try again later.');
}
$this->set(compact('series'));
$this->viewBuilder()->setOption('serialize', 'series');
}
However, when I process this request instead of updating the titles with ids 1,2,3 it just tries to create new ones. If I try to edit an existing series with titles, this request does work as the titles association is already contained on the entity I guess? But if I try to create a new series or update one that has no titles, it simply makes new ones.
Any help would be appreciated! Thanks.
Kez
2
Managed to get this working with an afterMarshal
hook but it feels very hacky somehow? I would certainly be happy to find a more elegant solution!
/**
* @param \Cake\Event\EventInterface $event event
* @param \Cake\Datasource\EntityInterface $entity event
* @param \ArrayObject $data data
* @param \ArrayObject $options options
* @return void
*/
public function afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $data, ArrayObject $options): void
{
if (!empty($data['titles'])) {
$titleIds = array_map(fn($title) => $title['id'], $data['titles']);
$titles = $this->Titles->find()->where(['Titles.id IN' => $titleIds]);
$titles = $this->Titles->patchEntities($titles, $data['titles']);
$entity->titles = $titles;
}
}
jmcd73
3
You might try setting Titles.id
to be an accessible field
public function add(): void
{
$this->request->allowMethod(['post']);
$series = $this->Series->newEmptyEntity();
$series = $this->Series->patchEntity($series, $this->request->getData(), [
'associated' => [
'Titles' => [
'accessibleFields' => [
// turns insert into update
'id' => true
],
// This turns off validation but you can edit or create a custom validation rule
// to suit the situation
'validate' => false,
],
],);
if ($this->Series->save($series)) {
$series = $this->Series->get($series->id, contain: ['Titles']);
} else {
throw new InternalErrorException('Could not save the series. Please try again later.');
}
$this->set(compact('series'));
$this->viewBuilder()->setOption('serialize', 'series');
}
1 Like
Kez
4
That did the trick, thanks!
1 Like