On cakePHP 3.4, i have 3 tables with a belongs to and has many relationship: Ingredients, Products and IngredientsProducts:
class IngredientsTable extends Table
{
public function initialize(array $config)
{
// Use through option because it looks like you
// have additional data on your IngredientsProducts table
$this->belongsToMany('Products', [
'through' => 'IngredientsProducts',
]);
}
}
class ProductsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Ingredients', [
'through' => 'IngredientsProducts',
]);
}
}
class IngredientsProductsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Ingredients');
$this->belongsTo('Products');
}
}
What I want to accomplish is that when I insert a new product, I would like to insert also the ingredient_id(s) and field qty of each ingredient that is related to that product, on the IngredientsProducts joiner table.
I've been reading the cookbook, and saw that when saving aditional data on the joiner table (in my case the field qty as stated below), you have to use the _joinData property, so my add view looks like this:
<?php
$this->Form->create($product);
// fields of the Products table
echo $this->Form->control('name',array('class' => 'form-control'));
echo $this->Form->control('retail_price');
echo $this->Form->control('best_before');
echo $this->Form->control('comments');
// ingredient_id and qty fields of the ingredients_products table
echo $this->Form->control('ingredients.0._joinData.ingredient_id',['options' => $ingredients);
echo $this->Form->control('ingredients.0._joinData.qty');
//and repetition of these last two as ingredients.1._joinData to ingredients.N._joinData
$this->Form->button(__('Save'));
$this->Form->end();
?>
and on the controller the add method looks like this:
$product = $this->Products->newEntity();
if ($this->request->is('post')) {
$product = $this->Products->patchEntity($product, $this->request->getData(),[
'associated' => ['Ingredients._joinData']
]);
if ($this->Products->save($product)) {
$this->Flash->success(__('The product has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The product could not be saved. Please, try again.'));
}
$this->set(compact('product'));
$this->set('_serialize', ['product']);
However when I submit it doesn't save anything. The debug displays the following being posted:
object(App\Model\Entity\Product) {
'name' => 'salada',
'retail_price' => (float) 23,
'best_before' => (int) 234,
'comments' => 'wer',
'directions' => 'werwewerer',
'ingredients' => [
(int) 0 => object(App\Model\Entity\Ingredient) {
'_joinData' => object(Cake\ORM\Entity) {
'ingredient_id' => (int) 4,
'qty' => (float) 100,
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'ingredient_id' => true,
'qty' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'IngredientsProducts'
},
'[new]' => true,
'[accessible]' => [
'*' => true,
'id' => false
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => [
'ingredient_id' => '4',
'qty' => '100'
]
],
'[virtual]' => [],
'[errors]' => [
'name' => [
'_required' => 'This field is required'
]
],
'[invalid]' => [],
'[repository]' => 'Ingredients'
},
(int) 1 => object(App\Model\Entity\Ingredient) {
'_joinData' => object(Cake\ORM\Entity) {
'ingredient_id' => (int) 5,
'qty' => (float) 200,
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'ingredient_id' => true,
'qty' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'IngredientsProducts'
},
'[new]' => true,
'[accessible]' => [
'*' => true,
'id' => false
],
'[dirty]' => [
'_joinData' => true
],
'[original]' => [
'_joinData' => [
'ingredient_id' => '5',
'qty' => '200'
]
],
'[virtual]' => [],
'[errors]' => [
'name' => [
'_required' => 'This field is required'
]
],
'[invalid]' => [],
'[repository]' => 'Ingredients'
}
],
'[new]' => true,
'[accessible]' => [
'*' => true,
'id' => false
],
'[dirty]' => [
'name' => true,
'retail_price' => true,
'best_before' => true,
'comments' => true,
'directions' => true,
'ingredients' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Products'
}
Does anybody happen to know how to make this work?
just in case here are the table structures on mySQL:
TABLE `Ingredients` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`category_id` int(11) NOT NULL,
`measure_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Table products
TABLE `Products` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`retail_price` float NOT NULL,
`best_before` int(11) NOT NULL,
`comments` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
TABLE `ingredients_products` (
`ingredient_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`qty` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Any help or directions would be kindly appreciated! Thanks
errors
infoWhenever saving an entity doesn't work, check the errors
info:
'[errors]' => [
'name' => [
'_required' => 'This field is required'
]
],
Your validation rules define the Ingredients.name
field as required, but it's not present in your form, hence saving fails.
Take a closer look at the examples in the docs for saving join data, the data structure looks a little different to yours.
From your usage of ingredient_id
I suspect that you want to link the product to existing ingredients, placing ingredient_id
in the _joinData
however is not how this works. Editing/linking existing records requires the primary key of the record to be present in either the special _ids
key, or on the primary key field of the target association (Ingredients
).
So in your case where you want to store additional join data, you have to use the id
property for the ingredient, that way the marshaller knows that it needs to load an existing record:
echo $this->Form->control('ingredients.0.id', [
// defining the type is required, as the input type
// guessing only recognizes `_ids` fields, or fields
// with `_id` appended as possible select types
'type' => 'select'
'options' => $ingredients
]);
echo $this->Form->control('ingredients.0._joinData.qty');