Bonjour,
Afin de simplifier et clarifier la situation, j'ai créé un nouveau projet avec juste le nécessaire :
Les tables :
CREATE TABLE users (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
nom varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
CREATE TABLE animals (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
genre varchar(255) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
CREATE TABLE IF NOT EXISTS animals_users (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
user_id int(10) unsigned NOT NULL,
animal_id int(10) unsigned NOT NULL,
nom varchar(255) NOT NULL,
PRIMARY KEY (id),
KEY user_id (user_id),
KEY animal_id (animal_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
J'ai généré la totalité du CRUD avec CakePhp
Les Modeles Tables:
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->table('users');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsToMany('Animals', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'animal_id',
'joinTable' => 'animals_users',
'through' => 'animals_users',
]);
}
}
class AnimalsTable extends Table
{
public function initialize(array $config)
{
$this->table('animals');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsToMany('Users', [
'foreignKey' => 'animal_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'animals_users',
'through' => 'animals_users',
]);
}
}
class AnimalsUsersTable extends Table
{
public function initialize(array $config)
{
$this->table('animals_users');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Animals', [
'foreignKey' => 'animal_id',
'joinType' => 'INNER'
]);
}
}
Je vous ai fait grâce des validators qui n'ont rien de particuliers. La seule modification que j'ai apporté ce sont les conditions 'through'=> 'animals__*users' que j'ai ajouté conformément à la doc (http://book.cakephp.org/3.0/fr/orm/associations.html#utiliser-l-option-through)
Les Modeles Entity
class User extends Entity
{
protected $_accessible = [
'nom' => true,
'animals' => true,
];
}
class Animal extends Entity
{
protected $_accessible = [
'genre' => true,
'users' => true,
];
}
class AnimalsUser extends Entity
{
protected $_accessible = [
'user_id' => true,
'animal_id' => true,
'nom' => true,
'user' => true,
'animal' => true,
];
}
Rien à signaler, sauf que .... Je ne comprend pas le accessible sur 'animals' dans User. A mon sens, 'animals' ne peut (et ne doit) pas être modifié directement à partir de User. En changeant ça, j'obtiens des résultats plus cohérents dans le patchage de l'entité user ( mais pas concluants de toute façon).
Le edit du controleur User :
class UsersController extends AppController
{
...
public function edit($id = null)
{
$user = $this->Users->get($id, [
'contain' => ['Animals']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$user = $this->Users->patchEntity($user, $this->request->data,['associated'=>['Animals._joinData']]);
debug($this->request->data);
debug($user);
die();
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
}
$animals = $this->Users->Animals->find('list', ['keyField' => 'id', 'valueField' => 'genre', 'limit' => 200]);
$this->set(compact('user', 'animals'));
$this->set('_serialize', ['user']);
}
....
Le formulaire edit :
( je pars sur celui-là, car il est plus facile de voir si les données requétées remplacent, ou pas, les données extraitent de la bdd)
<div class="users form large-10 medium-9 columns">
<?= $this->Form->create($user) ?>
<fieldset>
<legend><?= __('Edit User') ?></legend>
<?= $this->Form->input('nom') ?>
<fieldset>
<legend><?=__('Animal N°1') ?></legend>
<?= $this->Form->input('animals.0._joinData.user_id', ['type'=>'hidden']) ?>
<?= $this->Form->input('animals.0._joinData.nom') ?>
<?= $this->Form->input('animals.0._joinData.animal_id', ['label'=>__('Genre d\'animal'),'options' => $animals]) ?>
</fieldset>
</fieldset>
<?= $this->Form->button(__('Enregistrer')) ?>
<?= $this->Form->end() ?>
</div>
J'ai repris le _joinData.user_id en hidden. Des fois que ça servirai ...
Enfin, le résultat du debug de request->data et de l'entité du get après le patchage, AVEC $_accessible { 'animals' => true} dans le model entity User :
\src\Controller\UsersController.php (line 77)
[
'nom' => 'john doe',
'animals' => [
(int) 0 => [
'_joinData' => [
'user_id' => '1',
'nom' => 'Garfield',
'animal_id' => '1'
]
]
]
]
\src\Controller\UsersController.php (line 77)
object(App\Model\Entity\User) {
'id' => (int) 1,
'nom' => 'john doe',
'animals' => [
(int) 0 => object(App\Model\Entity\Animal) {
'[new]' => true,
'[accessible]' => [
'genre' => true,
'users' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [
'genre' => [
'_required' => 'This field is required'
]
],
'[repository]' => 'Animals'
}
],
'[new]' => false,
'[accessible]' => [
'nom' => true,
'animals' => true
],
'[dirty]' => [
'animals' => true
],
'[original]' => [
'animals' => [
(int) 0 => object(App\Model\Entity\Animal) {
'id' => (int) 1,
'genre' => 'chat',
'_joinData' => object(App\Model\Entity\AnimalsUser) {
'animal_id' => (int) 1,
'id' => (int) 1,
'user_id' => (int) 1,
'nom' => 'Felix',
'[new]' => false,
'[accessible]' => [
'user_id' => true,
'animal_id' => true,
'nom' => true,
'user' => true,
'animal' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'animals_users'
},
'[new]' => false,
'[accessible]' => [
'genre' => true,
'users' => true,
'_joinData' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Animals'
}
]
],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users'
}
Le même, SANS $_accessible { 'animals' => true} dans le model entity User :
\src\Controller\UsersController.php (line 77)
[
'nom' => 'john doe',
'animals' => [
(int) 0 => [
'_joinData' => [
'user_id' => '1',
'nom' => 'Garfield',
'animal_id' => '1'
]
]
]
]
\src\Controller\UsersController.php (line 77)
object(App\Model\Entity\User) {
'id' => (int) 1,
'nom' => 'john doe',
'animals' => [
(int) 0 => object(App\Model\Entity\Animal) {
'id' => (int) 1,
'genre' => 'chat',
'_joinData' => object(App\Model\Entity\AnimalsUser) {
'animal_id' => (int) 1,
'id' => (int) 1,
'user_id' => (int) 1,
'nom' => 'Felix',
'[new]' => false,
'[accessible]' => [
'user_id' => true,
'animal_id' => true,
'nom' => true,
'user' => true,
'animal' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'animals_users'
},
'[new]' => false,
'[accessible]' => [
'genre' => true,
'users' => true,
'_joinData' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Animals'
}
],
'[new]' => false,
'[accessible]' => [
'nom' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Users'
}
Ce dernier semble plus conforme à mes attentes, mais 'Garfield' remplace toujours pas 'Felix' ...
Bref, y un truc qui est pas clair ! En tout cas dans mon esprit, et peut-être dans la doc...