CakePHP 4 – saving associated data not working with 3 simple tables

Issue

I have an application in CakePHP 4 and am having problems saving associated model data. I have read Saving Associated Data in the Cake documentation but it’s very unclear how this applies especially in my use-case.

The application has 3 tables which are relevant to this question:

  • items
  • sizes
  • items_sizes_wanted

The application allows users to request items of clothing (items) and the form to input/save such an item has a dropdown of different sizes (sizes). Each size has a unique ID. A user can select one or more size when saving an item. The items_sizes_wanted table is supposed to hold one (or more) rows depending on the sizes the user selected, with the corresponding item ID. For example if they saved sizes 2, 3 and 4 for Item 999 there would be 3 rows in this table:

size_id | item_id
--------|---------
2       | 999
3       | 999
4       | 999

The code has been baked and the associations in the Table classes look ok:

// src/Model/Table/ItemsSizesWantedTable.php
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('items_sizes_wanted');

        $this->belongsTo('Items', [
            'foreignKey' => 'item_id',
            'joinType' => 'INNER',
        ]);
        $this->belongsTo('Sizes', [
            'foreignKey' => 'size_id',
            'joinType' => 'INNER',
        ]);
    }

The Entity class for the item also looks ok:

// src/Model/Entity/Item.php

// @property \App\Model\Entity\ItemsSizesWanted[] $items_sizes_wanted

protected $_accessible = [
    // ...
        'items_sizes_wanted' => true,
    // ...
];

In the form where the item gets saved (src/templates/Items/add.php) I have used the Form Helper and named it using dot notation:

<?php
// Note that $sizes is an array of key/value pairs from the
// 'sizes' table.
?>
<?= $this->Form->control('items_sizes_wanted.size_id', ['options' => $sizes, 'multiple' => 'multiple']) ?>

When rendered in the browser this produces a valid array syntax name. The <option>‘s rendered inside all have valid ID’s, i.e. the ones from the sizes table.

<select name="items_sizes_wanted[size_id]" multiple="multiple">

When I save the data in my Controller (src/Controller/ItemsController.php) using the following:

public function add()
{
    $item = $this->Items->newEmptyEntity();

    if ($this->request->is('post')) {
        $item = $this->Items->patchEntity($item, $this->request->getData());

        // Edit: some of the entity properties are manually set at this point, e.g.
        $item->item_status = 'Wanted';

        if ($this->Items->save($item)) {
            $this->Flash->success(__('Your item has been listed.'));
        }
    }
}

The data is saved correctly to the items table and the flash success message, "Your item has been listed." is displayed in the browser.

But – no data is written to items_sizes_wanted.

I’m unsure why this is. The linked docs don’t specifically say how the Form Helper should be used, so I’m assuming my syntax for that form field is correct, but it might not be.

If I debug the entity after pressing Save using debug($item); die; in the Controller it has 'items_sizes_wanted' => [ ] even though I selected multiple size options using the form.

Please can somebody help as I’m lost as to what’s going wrong here?

Solution

Disclaimer: I do not know CakePHP well, but I think I either know the solution, or can at least point you in the right direction.

The reason you’re only getting the one selected size, instead of multiple, is because the generated input fields are named items_sizes_wanted[size_id], however, in order for PHP to parse multiple values into an array, they need to be named items_sizes_wanted[size_id][]. When the request parameter ends with [], then PHP will properly parse all request properties into an array.

For example: Here’s var_dump($_POST); of a request containing the POST body of items_sizes_wanted[size_id][]=A&items_sizes_wanted[size_id][]=B&items_sizes_wanted[size_id][]=C

array (size=1)
  'items_sizes_wanted' => 
    array (size=1)
      'size_id' => 
        array (size=3)
          0 => string 'A' (length=1)
          1 => string 'B' (length=1)
          2 => string 'C' (length=1)

Compare that to a POST body of items_sizes_wanted[size_id]=A&items_sizes_wanted[size_id]=B&items_sizes_wanted[size_id]=C (notice the empty braces at the end of each have been removed):

array (size=1)
  'items_sizes_wanted' => 
    array (size=1)
      'size_id' => string 'C' (length=1)

This is the part where I’m less familiar with CakePHP. I looked over the code
for CakePHP’s FormHelper, and based on the template code, I think you need to change your form code in add.php to be something like this (reformatted for readability):

<?php
// Note that $sizes is an array of key/value pairs from the
// 'sizes' table.
?>
<?= 
$this->Form->control(
    'items_sizes_wanted.size_id', 
    [
        'options' => $sizes, 
        'multiple' => 'multiple'
        'type' => 'selectMultiple'
    ]
) 
?>

Based on the __call() method in FormHelper, You might also be able to write it like this:

$this->Form->selectMultiple(
    'items_sizes_wanted.size_id', 
    [
        'options' => $sizes, 
        'multiple' => 'multiple'
    ]
);

However, I’m not familiar with the nuances between creating a control($fieldName, $options) and inputType($fieldName, $options), so they might produce different outputs.

Answered By – 404 Not Found

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published