Problem while tempting to save related data

Hello community! i’m new in cakePHP 3.x and i’m trying to find some help here 'cause i’m stuck with this.

I have 2 tables with their respective models.

Table 1: users Model name: UsersTable
Table 2: contact_types Model name: ContactTypesTable

I have also a third table: users_contact_types with it model UsersConctactTypesTable
This table i used to make the relation between users and their contact types. The fields of that table are: user_id, contact_type_id and value.

For example:
Supose we have a user named John with ID = 1 and supose we have 3 different contact types (telephone,email and skype…with ID 1,2 and 3 respectively)

Aim to achieve:
Store in the relation table (users_contact_types) something like this:

user_id contact_type_id value
1 2 agasiadolfo@gmail.com
1 3 agasi.adolfo

and so on.

This relation (according to the documentation Associations - Linking Tables Together - 3.10 ) is a belongs-to-many relation.

I followed all the steps to create the models, tables, and tempted to save the data in the controller but still having some issues to make this work.

I also read in the documentation(Saving Data - 3.10) that to save some extra fields we have to use _joinData but i don’t understand how to make it work.

Here i copy my files so you can have a look and tell me what you think.

INITIALIZE METHOD OF USERSTABLE MODEL:

public function initialize(array $config) {

    parent::initialize($config);
    $this->table('users');
    $this->displayField('id');
    $this->primaryKey('id');
    $this->addBehavior('Timestamp');
    $this->addBehavior('Acl.Acl', ['type' => 'requester']);
    $this->belongsToMany('ContactTypes', [
        'through' => 'UsersContactTypes',
    ]);

}

INITIALIZE METHOD OF CONTACTTYPES MODEL

public function initialize(array $config) {
parent::initialize($config);

    $this->table('contact_types');
    $this->displayField('name');
    $this->primaryKey('id');
    $this->belongsToMany('Users', [
        'through' => 'UsersContactTypes',
    ]);
}

INITIALIZE METHOD OF USERSCONTACTTYPES MODEL

public function initialize(array $config)
{
parent::initialize($config);

    $this->table('users_contact_types');
    $this->displayField('user_id');
    $this->primaryKey(['user_id', 'contact_type_id']);
    $this->belongsTo('Users', [
        'foreignKey' => 'user_id',
        'joinType' => 'INNER',
        'className' => 'UsersCrudAuth.Users'
    ]);
    $this->belongsTo('ContactTypes', [
        'foreignKey' => 'contact_type_id',
        'joinType' => 'INNER',
        'className' => 'UsersCrudAuth.ContactTypes'
    ]);
}

THIS IS MY Users/add.ctp

  • <?= __('Actions') ?>
  • <?= $this->Html->link(__('List Users'), ['action' => 'index']) ?>
  • <?= $this->Html->link(__('List Aro'), ['controller' => 'Aros', 'action' => 'index']) ?>
  • <?= $this->Html->link(__('New Aro'), ['controller' => 'Aros', 'action' => 'add']) ?>
  • <?= $this->Html->link(__('List Contact Types'), ['controller' => 'ContactTypes', 'action' => 'index']) ?>
  • <?= $this->Html->link(__('New Contact Type'), ['controller' => 'ContactTypes', 'action' => 'add']) ?>
<?= $this->Form->create($user) ?> <?= __('Add User') ?> <?php echo $this->Form->input('username'); echo $this->Form->input('name'); echo $this->Form->input('surname'); echo $this->Form->input('gender'); echo $this->Form->input('password'); echo $this->Form->input('group_id', ['options' => $groupsList]); echo $this->Form->input('status');
        /** For now i render the fields  like this, but then i'm going to use a table with a 'add register' button that creates a new node to the table incrementing the id    */

echo $this->Form->input(‘contact_types.0._id’, [‘options’ => $contactTypes]);
echo $this->Form->input(“contact_types.0._joinData.value”);
echo $this->Form->input(‘contact_types.1._id’, [‘options’ => $contactTypes]);
echo $this->Form->input(“contact_types.1._joinData.value”);> ?>

</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>

And my UsersController.php (only the add method for now)

public function add() {
$user = $this->Users->newEntity();
if ($this->request->is(‘post’)) {
$user = $this->Users->patchEntity($user, $this->request->data);

        /** This is the posted data from the form */
        debug($this->request->data);
        /** Here i tempt to save the user model and the associated model whith it _joinData field */
        debug($this->Users->save($user,
            ['associated' => ['ContactTypes','ContactTypes._joinData']]
        ));
        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.'));
        }
    }
    $groupsTable = TableRegistry::get('Groups');
    $groupsList = $groupsTable->find('list', ['limit' => 200]);
    $contactTypes = TableRegistry::get('ContactTypes')->find('list', ['limit' => 200]);
    $this->set(compact('user', 'contactTypes','groupsList'));
    $this->set('_serialize', ['user']);
}

Also i add here the debug of the request->data sended from the form.

> /plugins/UsersCrudAuth/src/Controller/UsersController.php (line 105)
> [
> 	'username' => 'john',
> 	'name' => '',
> 	'surname' => '',
> 	'gender' => 'm',
> 	'password' => 'john',
> 	'group_id' => '2',
> 	'status' => '',
> 	'contact_types' => [
> 		(int) 0 => [
> 			'_id' => '5',
> 			'_joinData' => [
> 				'value' => 'john@doe.com'
> 			]
> 		],
> 		(int) 1 => [
> 			'_id' => '4',
> 			'_joinData' => [
> 				'value' => 'john.doe'
> 			]
> 		]
> 	]
> ]
> /plugins/UsersCrudAuth/src/Controller/UsersController.php (line 110)
> false

Well, i hope you can help me with this because i can’t figure out whats going on here.

By the way, thanks for cooperating.

It’s just a hunch, but i think the problem is that you didn’t inform the contact_type_id.
insteand _id try
$this->Form->input('contact_types.1.contact_type_id', ['options' => $contactTypes]);

/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 105)
[
	'username' => 'john',
	'name' => '',
	'surname' => '',
	'gender' => 'm',
	'password' => 'john',
	'group_id' => '2',
	'status' => '',
	'contact_types' => [
		(int) 0 => [
			'contact_type_id' => '5',
			'_joinData' => [
				'value' => 'john@doe.com'
			]
		],
		(int) 1 => [
			'contact_type_id' => '4',
			'_joinData' => [
				'value' => 'john.doe'
			]
		]
	]
]
/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 110)
false

Thanks Alysson, i’have tried your approach but still not saving. Here is the new request->data with that changes.

maybe contact_types.1._joinData.contact_type_id

I also think like you. I feel that the problem is that the object being send is malformed. But i don’t know how to solve that problem. Do you know where can i refer to look for some examples?
The cakePHP documentation doesn’t have clear examples for that.

/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 105)
[
	'username' => 'john',
	'name' => '',
	'surname' => '',
	'gender' => 'm',
	'password' => 'john',
	'group_id' => '2',
	'status' => '',
	'contact_types' => [
		(int) 0 => [
			'_joinData' => [
				'contact_type_id' => '5',
				'value' => 'john@doe.com'
			]
		],
		(int) 1 => [
			'_joinData' => [
				'contact_type_id' => '4',
				'value' => 'john.doe'
			]
		]
	]
]
/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 110)
false

I tried that one, but no success.

I had some time ago a problem like this. First i think that you may need to use the the ‘through’ Option if you use that you don’t need the _joinData (i think)

Also to debug, don’t debug the request data, add the associated key in patchEntity and debug the resulting entity. If it all done right the contactTypes should be an array of entities instead of an array of arrays.

Here: Saving Data - 3.10

I’m not sure how to implement this using the form helper, but this problably works

$data = [
    'contact_types' => [
        '_ids' => [5, 4]
    ]
];
$user = $this->Users->patchEntity($user, $data);

maybe $this->Form->input('contact_types._ids[]', ['options' => $contactTypes]);

Hello Raul, i’m using the through option in the model definition. But that doesn’t meant that you don’t need the _joinData option. The _joinData is used to add some extra fields to the relation model so ,for example, i can save de ‘value’ (in my case)

Refering to the debug, can you write down the code to add the key to the patchEntity? I tried to add it like this:
$user = $this->Users->patchEntity($user, $this->request->data,
[‘associated’ => [‘ContactTypes’,‘ContactTypes._joinData’]]
);

But i don’t know if you were talking about doing that way.

Alysson, this approach is almost correct. Now the data of the relation is being saved. But the extra field i added (value) is null in the database. Have you any idea why?

This is the new debug were you can see that now the save object is being set.

/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 105)
[
	'username' => 'jane',
	'name' => '',
	'surname' => '',
	'gender' => 'f',
	'password' => 'jane',
	'group_id' => '2',
	'status' => '',
	'contact_types' => [
		'_ids' => [
			(int) 0 => '5',
			(int) 1 => '4'
		],
		(int) 0 => [
			'_joinData' => [
				'value' => 'jane@doe.com'
			]
		],
		(int) 1 => [
			'_joinData' => [
				'value' => 'jane.doe'
			]
		]
	]
]
/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 110)
object(UsersCrudAuth\Model\Entity\User) {

	'username' => 'jane',
	'name' => '',
	'surname' => '',
	'gender' => 'f',
	'password' => '$2y$10$EEPZZNIBvhJWYJjsQnQZWuISi2j9BXABQ0PsgKZGDstOzNYq8NILC',
	'group_id' => (int) 2,
	'status' => '',
	'contact_types' => [
		(int) 0 => object(Cake\ORM\Entity) {

			'id' => (int) 5,
			'name' => 'E-mail',
			'_joinData' => object(Cake\ORM\Entity) {

				'user_id' => (int) 57,
				'contact_type_id' => (int) 5,
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'UsersContactTypes'
			
			},
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [],
			'[original]' => [],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'ContactTypes'
		
		},
		(int) 1 => object(Cake\ORM\Entity) {

			'id' => (int) 4,
			'name' => 'Facebook',
			'_joinData' => object(Cake\ORM\Entity) {

				'user_id' => (int) 57,
				'contact_type_id' => (int) 4,
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'UsersContactTypes'
			
			},
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [],
			'[original]' => [],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'ContactTypes'
		
		}
	],
	'created' => object(Cake\I18n\Time) {

		'time' => '2016-05-14T02:17:18+00:00',
		'timezone' => 'UTC',
		'fixedNowTime' => false
	
	},
	'modified' => object(Cake\I18n\Time) {

		'time' => '2016-05-14T02:17:18+00:00',
		'timezone' => 'UTC',
		'fixedNowTime' => false
	
	},
	'id' => (int) 57,
	'[new]' => false,
	'[accessible]' => [
		'*' => true,
		'contact_types' => true,
		'value' => true
	],
	'[dirty]' => [],
	'[original]' => [
		'contact_types' => [
			(int) 0 => object(Cake\ORM\Entity) {

				'id' => (int) 5,
				'name' => 'E-mail',
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'ContactTypes'
			
			},
			(int) 1 => object(Cake\ORM\Entity) {

				'id' => (int) 4,
				'name' => 'Facebook',
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'ContactTypes'
			
			}
		]
	],
	'[virtual]' => [],
	'[errors]' => [],
	'[invalid]' => [],
	'[repository]' => 'UsersCrudAuth.Users'

}

Maybe

$data = [
    'contact_types' => [
        ['id' => 4, 'value' => 'aaa'],
        ['id' => 5, 'value' => 'bbb']
    ]
];

That looks good, but i don’t know how to implement it. Can you please give me a hint? I mean using the Form Helper

$this->Form->input('contact_types.0.id', ['options' => $contactTypes]);
$this->Form->input('contact_types.0.value');
$this->Form->input('contact_types.1.id', ['options' => $contactTypes]);
$this->Form->input('contact_types.1.value');

i just tried this. But now the thing is that the options are not being displayed. Now in the form the drawed html is:

<input type="hidden" name="contact_types[0][id]" options="SELECT ContactTypes.id ASContactTypes__id, ContactTypes.name ASContactTypes__nameFROM contact_types ContactTypes LIMIT 200" id="contact-types-0-id">

This thing It’s kinda tricky.

$contactTypes = TableRegistry::get('ContactTypes')->find('list', ['limit' => 200])->all();

Now this is the rendered html. Still not being able to ‘see’ the dropdown with the values of the contact types.

<input type="hidden" name="contact_types[0][id]" options="(object)Cake\Datasource\ResultSetDecorator" id="contact-types-0-id">

What should i do?

Add ‘type’ => ‘select’ with the options

Well, now the rendered html is ok i think.

`

Celular E-mail Facebook Skype Teléfono fijo `

And this is the debug.

/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 105)
[
	'username' => 'jany',
	'name' => '',
	'surname' => '',
	'gender' => 'f',
	'password' => 'jany',
	'group_id' => '1',
	'status' => '',
	'contact_types' => [
		(int) 0 => [
			'id' => '5',
			'value' => 'jany@doe.com'
		],
		(int) 1 => [
			'id' => '4',
			'value' => 'jany.doe'
		]
	]
]
/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 110)
object(UsersCrudAuth\Model\Entity\User) {

	'username' => 'jany',
	'name' => '',
	'surname' => '',
	'gender' => 'f',
	'password' => '$2y$10$Ejs6TDlEATIc2dn8i3WoCe.Jyg0blkfUOtaFB1BqcQhiDrHml4w0e',
	'group_id' => (int) 1,
	'status' => '',
	'contact_types' => [
		(int) 0 => object(Cake\ORM\Entity) {

			'id' => (int) 5,
			'name' => 'E-mail',
			'value' => 'jany@doe.com',
			'_joinData' => object(Cake\ORM\Entity) {

				'user_id' => (int) 59,
				'contact_type_id' => (int) 5,
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'UsersContactTypes'
			
			},
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [],
			'[original]' => [],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'ContactTypes'
		
		},
		(int) 1 => object(Cake\ORM\Entity) {

			'id' => (int) 4,
			'name' => 'Facebook',
			'value' => 'jany.doe',
			'_joinData' => object(Cake\ORM\Entity) {

				'user_id' => (int) 59,
				'contact_type_id' => (int) 4,
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'UsersContactTypes'
			
			},
			'[new]' => false,
			'[accessible]' => [
				'*' => true
			],
			'[dirty]' => [],
			'[original]' => [],
			'[virtual]' => [],
			'[errors]' => [],
			'[invalid]' => [],
			'[repository]' => 'ContactTypes'
		
		}
	],
	'created' => object(Cake\I18n\Time) {

		'time' => '2016-05-14T15:33:22+00:00',
		'timezone' => 'UTC',
		'fixedNowTime' => false
	
	},
	'modified' => object(Cake\I18n\Time) {

		'time' => '2016-05-14T15:33:22+00:00',
		'timezone' => 'UTC',
		'fixedNowTime' => false
	
	},
	'id' => (int) 59,
	'[new]' => false,
	'[accessible]' => [
		'*' => true,
		'contact_types' => true,
		'value' => true
	],
	'[dirty]' => [],
	'[original]' => [
		'contact_types' => [
			(int) 0 => object(Cake\ORM\Entity) {

				'id' => (int) 5,
				'name' => 'E-mail',
				'value' => 'jany@doe.com',
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [
					'value' => true
				],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'ContactTypes'
			
			},
			(int) 1 => object(Cake\ORM\Entity) {

				'id' => (int) 4,
				'name' => 'Facebook',
				'value' => 'jany.doe',
				'[new]' => false,
				'[accessible]' => [
					'*' => true
				],
				'[dirty]' => [
					'value' => true
				],
				'[original]' => [],
				'[virtual]' => [],
				'[errors]' => [],
				'[invalid]' => [],
				'[repository]' => 'ContactTypes'
			
			}
		]
	],
	'[virtual]' => [],
	'[errors]' => [],
	'[invalid]' => [],
	'[repository]' => 'UsersCrudAuth.Users'

}

But still not saving the value field. Do you have any clue?

Hi Adolfo,

I think what you are mainly facing here is a concept issue. It doesn’t make much sense to a User belongsToMany ContactTypes. It would make more sense for a User hasMany Contacts which in turn belongsTo ContactTypes.

This way you will make the form much easier. For example:

$this->Form->input('contacts.0.contact_type_id');
$this->Form->input('contacts.0.value');

You would then have:

User hasMany Contacts
Contacts belongsTo ContactTypes

I’ll not go through more Table definitions as I think you can figure them out by yourself. But feel free to ask anything about this approach.

Best,
Pedro.

1 Like

Thanks for the reply.

So i updated my models and now i have:

UsersTable.php

public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('users');
        $this->displayField('id');
        $this->primaryKey('id');

        $this->addBehavior('Timestamp');
        $this->addBehavior('Acl.Acl', ['type' => 'requester']);

        $this->hasMany('ContactTypes');
    }

ContactTypesTable.php

public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('contact_types');
        $this->displayField('name');
        $this->primaryKey('id');

        $this->belongsTo('Users');
    }

And the ContactTypesTable.php (that still being the same)

public function initialize(array $config)
    {
        parent::initialize($config);

        $this->table('users_contact_types');
        $this->displayField('user_id');
        $this->primaryKey(['user_id', 'contact_type_id']);

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER',
            'className' => 'UsersCrudAuth.Users'
        ]);
        $this->belongsTo('ContactTypes', [
            'foreignKey' => 'contact_type_id',
            'joinType' => 'INNER',
            'className' => 'UsersCrudAuth.ContactTypes'
        ]);
    }

In the add.ctp

            echo $this->Form->input('contacts.0.contact_type_id');
            echo $this->Form->input('contacts.0.value');

            echo $this->Form->input('contacts.1.contact_type_id');
            echo $this->Form->input('contacts.1.value');

And the UsersController.php

public function add() {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->data);

            /** This is the posted data from the form */
            debug($this->request->data);

            /** Here i tempt to save the user model and the associated model whith it _joinData field */
            debug($this->Users->save($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.'));
            }
        }

        $groupsTable = TableRegistry::get('Groups');
        $groupsList = $groupsTable->find('list', ['limit' => 200]);

        $contactTypes = TableRegistry::get('ContactTypes')->find('list', ['limit' => 200])->all();
        $this->set(compact('user', 'contactTypes','groupsList'));
        $this->set('_serialize', ['user']);
    }

But this, is still failing. Maybe i misunderstood what you tried to explain me.

The debug of the request->data sended to the controller is now:

/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 124)
[
	'username' => 'John',
	'name' => '',
	'surname' => '',
	'gender' => 'm',
	'password' => 'john',
	'group_id' => '2',
	'status' => '',
	'contacts' => [
		(int) 0 => [
			'contact_type_id' => '5',
			'value' => 'john@doe.com'
		],
		(int) 1 => [
			'contact_type_id' => '4',
			'value' => 'john.doe'
		]
	]
]
/plugins/UsersCrudAuth/src/Controller/UsersController.php (line 129)
false

Do i need to add something else to the models definition?

Thanks for any reply.