I am new to php frameworks and I've started to learn cakephp few days ago, but even after reading cakephp cookbook about saving belongsToMany associations I still don't know how to implement it in my situation.
I have three tables:
clients:
id | firstname | lastname | phone | email
interests:
id | text | comment | status_id | created_at
clients_interests:
client_id | interest_id
Models and entities were baked so I don't think it's something wrong there, but here is the code:
ClientsTable.php
class ClientsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('clients');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsToMany('Interests', [
'foreignKey' => 'client_id',
'targetForeignKey' => 'interest_id',
'joinTable' => 'clients_interests'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', 'create');
$validator
->scalar('firstname')
->maxLength('firstname', 64)
->requirePresence('firstname', 'create')
->allowEmptyString('firstname', false);
$validator
->scalar('middlename')
->maxLength('middlename', 64)
->allowEmptyString('middlename');
$validator
->scalar('lastname')
->maxLength('lastname', 64)
->requirePresence('lastname', 'create')
->allowEmptyString('lastname', false);
$validator
->scalar('phone')
->maxLength('phone', 24)
->requirePresence('phone', 'create')
->allowEmptyString('phone', false)
->add('phone', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator
->email('email')
->allowEmptyString('email');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
$rules->add($rules->isUnique(['phone']));
return $rules;
}
}
InterestsTable.php
class InterestsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('interests');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Statuses', [
'foreignKey' => 'status_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Clients', [
'foreignKey' => 'interest_id',
'targetForeignKey' => 'client_id',
'joinTable' => 'clients_interests'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', 'create');
$validator
->scalar('text')
->maxLength('text', 128)
->requirePresence('text', 'create')
->allowEmptyString('text', false);
$validator
->scalar('comment')
->maxLength('comment', 128)
->allowEmptyString('comment');
$validator
->date('created_at')
->requirePresence('created_at', 'create')
->allowEmptyDate('created_at', false);
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['status_id'], 'Statuses'));
return $rules;
}
public function getAll($id)
{
$connection = ConnectionManager::get('default');
$results = $connection
->execute('SELECT i.*, s.name status_name, s.classname status_classname FROM interests i INNER JOIN clients_interests ci ON ci.interest_id=i.id AND ci.client_id=:client_id INNER JOIN statuses s ON i.status_id=s.id ORDER BY created_at DESC;', ['client_id' => $id])
->fetchAll('assoc');
return $results;
}
}
ClientsInterestsTable.php
class ClientsInterestsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('clients_interests');
$this->setDisplayField('client_id');
$this->setPrimaryKey(['client_id', 'interest_id']);
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Interests', [
'foreignKey' => 'interest_id',
'joinType' => 'INNER'
]);
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['client_id'], 'Clients'));
$rules->add($rules->existsIn(['interest_id'], 'Interests'));
return $rules;
}
}
Client entity - Client.php
class Client extends Entity
{
protected $_accessible = [
'firstname' => true,
'middlename' => true,
'lastname' => true,
'phone' => true,
'email' => true
];
}
Interest entity - Interest.php
class Interest extends Entity
{
protected $_accessible = [
'text' => true,
'comment' => true,
'status_id' => true,
'created_at' => true,
'clients_interest' => true,
'status' => true
];
}
ClientsInterests entity - ClientsInterests.php
class ClientsInterest extends Entity
{
protected $_accessible = [
'client' => true,
'interest' => true
];
}
And then in my controller I get all the data from a form. Interest entity after patchEntity() gets all its data besides client data for joinTable. And after save method new interest in interests table is created but there is no new row in clients_interests table. I've tried a lot of things from stackoverflow but it didn't work because I can't understand the mechanism of saving associated data even after reading the manual. Could anyone explain me in simple words how to save belongsToMany associated data?
Here is the code of my save method in InterestsController.php:
public function add()
{
$this->request->allowMethod(['ajax', 'post']);
$this->response->withDisabledCache();
$interest = $this->Interests->newEntity();
$interest = $this->Interests->patchEntity($interest, $this->request->getData(),
['associated'=>['Clients._joinData']]
);
if($this->Interests->save($interest)) {
$result = ['error' => null, 'error_text' => 'SUCCESSFUL'];
} else {
$result = ['error' => null, 'error_text' => 'ERRORS ERRORS ERRORS'];
}
$result = ['error' => null, 'error_text' => 'ERRORS ERRORS ERRORS'];
$this->set('result', $result);
$this->set('_serialize', ['result']);
}
And in my template I use this input for getting client_id:
<?php echo $this->Form->control('clients.0.client_id', ['hidden', 'type'=>'text','id'=>'client-id', 'class'=>'form-control']); ?>
Request data looks like this: request data
And Interest entity after patchEntity() looks like this: Interest entity
If you want to add an 'interest', I think your request data should look differently.
Here is an example, but sorry, I haven't tested it, it's made so you get the idea : you have to tell CakePHP you want to create a Interest entity, and inside add a Client information (just like in the "Converting BelongsToMany Data" in "Saving Data" of book.cakephp.org/3.0/en/orm/saving-data.html).
$data = [
_csrfToken => "...",
'interests' => [
'text' => 'texttext',
'comment' => '',
'created_at' => '2019-01-10',
'status_id' => 2,
'clients' => [
'_ids' => [0],
]
];
Personally, I learnt a lot looking at the requests generated through the Form Helper from the automatically baked templates.
Related
I am using CakeDC Users and as part of the registration I would like the registrant to choose some checkbox values that correspond to another Model in the application. I am able to get the form to load the choices just fine, but I am unable to get it to save to the join table even though I have added all of the associations and accessibilities where it would seem relevant. The form is displaying in a similar way to other areas where I am saving the same type of association with a different model.
TypesTable.php
{
public function initialize(array $config): void
{
//$this->addBehavior('Timestamp');
$this->setDisplayField('type');
$this->setPrimaryKey('id');
$this->belongsToMany('Words');
$this->belongsToMany('Users');
$this->belongsTo('Languages', [
'foreignKey' => 'language_id',
'joinType' => 'INNER',
]);
}```
UsersTable.php (in the plugin folders)
```class UsersTable extends Table
{
...
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('username');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->addBehavior('CakeDC/Users.Register');
$this->addBehavior('CakeDC/Users.Password');
$this->addBehavior('CakeDC/Users.Social');
$this->addBehavior('CakeDC/Users.LinkSocial');
$this->addBehavior('CakeDC/Users.AuthFinder');
$this->hasMany('SocialAccounts', [
'foreignKey' => 'user_id',
'className' => 'CakeDC/Users.SocialAccounts',
]);
$this->hasMany('Types', [
'foreignKey' => 'user_id', 'targetForeignKey' => 'type_id',
'joinTable' => 'types_users']);
}```
In order for a "belongsToMany" association to work in this instance, both *Table.php files must have the relations listed as belongsToMany. Don't forget to make the field accessible in the Entities as well.
One also needs to place the associated array in the patchEntity function in the RegisterBehavior.php file.
UserTable.php (in CakeDC users)
public function initialize(array $config): void
{
...
$this->belongsToMany('Types', [
'foreignKey' => 'user_id', 'targetForeignKey' => 'type_id',
'joinTable' => 'types_users']);
}
TypesTable.php (in app)
class TypesTable extends Table
{
public function initialize(array $config): void
{
....
$this->belongsToMany('Users');
$this->belongsTo('Languages', [
'foreignKey' => 'language_id',
'joinType' => 'INNER',
]);
}
RegisterBehavior.php
$user = $this->_table->patchEntity(
$user,
$data,
['validate' => $validator ?: $this->getRegisterValidators($options), 'associated' => ['Types']]
);
I have read several Stack Overflow posts and the documentation pages about saving associated data in CakePHP 3, but I can't get my code to work. When creating a new Organisation, I also want to save the data of the new account (NewAccount) that belongs to that Organisation.
Below is a reproducible part of my code. The validation of the Organisations model are executed and if passed, the data is saved. The NewAccounts data does not get saved, and is not even being validated. I have already checked the naming conventions in all of these files, but I can't find anything that might be a problem anymore.
// Model/Table/OrganisationsTable.php
class OrganisationsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->hasOne('NewAccounts');
}
}
// Model/Table/NewAccountsTable.php
class NewAccountsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->belongsTo('Organisations', [
'foreignKey' => 'organisation_id'
]);
}
}
// Template/Organisations/admin_add.ctp
echo $this->Form->create($organisation);
echo $this->Form->control('new_account.email', [
'label' => __('Email address'),
'class' => 'form-control',
]);
echo $this->Form->control('new_account.name', [
'label' => __('Name'),
'class' => 'form-control',
]);
echo $this->Form->control('name', [
'label' => __('Organisation name'),
'div' => 'form-group',
'class' => 'form-control',
]);
// Controller/OrganisationsController.php
class OrganisationsController extends AppController
{
public function adminAdd() {
$organisation = $this->Organisations->newEntity();
if($this->request->is('post')) {
$this->Organisations->patchEntity($organisation, $this->request->getData(), [
'associated' => ['NewAccounts']
]);
if ($this->Organisations->save($organisation)) {
$id = $organisation->id;
debug("Success!");
}
else {
debug("Error");
}
}
$this->set(compact('organisation'));
}
}
CakePHP documentation I have referenced:
Saving Data
Form
Associations - Linking Tables Together
Validating Data
Entities
I forgot to make $organisation->new_account accessible:
// Model/Entity/Organisation.php
class Organisation extends Entity
{
protected $_accessible = [
// ...
'new_account' => true,
];
}
By doing this, the field is marked as to be safely assigned.
Entities: Mass Assignment (book) and EntityTrait::$_accessible (API docs)
I am learning having a problem insert records in Tables with association "hasOne", When I insert into the Table Users it should also insert in Table Customers but is only working with Users. I've looked people had similar problems but i really don't know what I am doing wrong. I appreciate any help
My User Model
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('users');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->hasOne('Customers', [
'foreignKey' => 'user_id'
]);
}
My Customer Model
class CustomersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('customers');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id'
]);
}
My request->getData()
[
'Users' => [
'Customers' => [
'first_name' => 'name',
'last_name' => 'test',
'gender' => 'Male',
'postcode' => '1234'
],
'username' => 'user1',
'password' => '12134'
],
'done' => '1'
]
My Action
public function addCustomer()
{
//Configure::write('debug',true);
// debug($this->request->getData());
$usersTable = TableRegistry::get('Users');
$user = $usersTable->newEntity();
if ($this->request->is('post')) {
$user = $usersTable->patchEntity($user,$this->request->getData(),['associated' => ['Customers']]);
if ($usersTable->save($user)) {
$this->Flash->success('The Customer has been saved.');
return $this->redirect(['action' => 'addCustomer']);
}
$this->Flash->error('Unable to add the Customer.');
}
}
I though that your User model can not have user_id as its foreinKey
I wanna make assoc with Locations via locations_ways join table what contain order and point (start / middle / end) of way.
In end I wanna get assoc
locations - all locations
end_location ONLY end location
start_location ONLY start location
Now for end_location and start_location I have array what containe one record. But I dont want write some like this: $way->end_location[0]->name;
How to process it?
class WaysTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Locations', [
'through' => 'LocationsWays',
'sort' => ['LocationsWays.position ASC'],
]);
$this->belongsToMany('LocationStartPoint', [
'className' => 'Locations',
'foreignKey' => 'way_id',
'targetForeignKey' => 'location_id',
'through' => 'LocationsWays',
'conditions' => ['LocationsWays.point' => 'start'],
]);
$this->belongsToMany('LocationEndPoint', [
'className' => 'Locations',
'foreignKey' => 'way_id',
'targetForeignKey' => 'location_id',
'through' => 'LocationsWays',
'conditions' => ['LocationsWays.point' => 'end'],
]);
}
}
class LocationsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Ways', [
'through' => 'LocationsWays',
]);
}
}
class LocationsWaysTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Ways', [
]);
$this->belongsTo('Locations', [
]);
}
}
Fetching func.
public function getWayById($id, $locale = 'uk')
{
I18n::locale($locale);
$item = $this->find()
->where(['Ways.id' => $id])
->contain(['Locations', 'LocationStartPoint', 'LocationEndPoint'])
->cache(function (Query $q) {
return 'way-' . md5(serialize($q->clause('where')));
}, 'orm')
->first();
return $item;
}
I want to make this tutorial http://miftyisbored.com/complete-tutorial-habtm-relationships-cakephp/ in cakePHP 3.0
I have 3 tables: recipes, ingredients and ingredients_recipes.
When making a recipe, I want to select ingredients. Then I want to store the recipe_id and ingredient_id in the ingredients_recipes table, but fail to do so. I think there's something wrong in my RecipesController. Can someone help me or point me in the right direction?
Problem:
$ingredients = $this->Recipes->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE MESSAGE "The recipe could not be saved. Please, try again."
$ingredients = $this->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE ERROR "Call to a member function find() on boolean"
When I do var dump (when using this $this->Recipes->Ingredients->find) I get this:
array(3) {
["recipe_name"]=> string(4) "Test"
["recipe_description"]=> string(4) "Test"
["Recipe"]=> array(1) {
["Ingredient"]=> array(1) { [0]=> string(1) "1" }
}
}
Tables:
CREATE TABLE `recipes` (
`recipe_id` int(11) NOT NULL auto_increment,
`recipe_name` varchar(255) NOT NULL,
`recipe_description` text NOT NULL,
PRIMARY KEY (`recipe_id`)
);
CREATE TABLE `ingredients` (
`ingredient_id` int(11) NOT NULL auto_increment,
`ingredient_name` varchar(255) NOT NULL,
`ingredient_description` text NOT NULL,
PRIMARY KEY (`ingredient_id`)
);
CREATE TABLE `ingredients_recipes` (
`ingredient_id` int(11) NOT NULL,
`recipe_id` int(11) NOT NULL,
PRIMARY KEY (`ingredient_id`,`recipe_id`)
);
Here's my code below:
Model > Entity:
Recipe
class Recipe extends Entity
{
protected $_accessible = [
'recipe_id' => true,
'recipe_name' => true,
'recipe_description' => true,
];
}
Ingredient
class Ingredient extends Entity
{
protected $_accessible = [
'ingredient_id' => true,
'ingredient_name' => true,
'ingredient_description' => true,
];
}
IngredientsRecipe
class IngredientsRecipe extends Entity
{
protected $_accessible = [
'ingredient' => true,
'recipe' => true,
];
}
Model > Table :
RecipesTable
class RecipesTable extends Table
{
public function initialize(array $config)
{
$this->table('recipes');
$this->displayField('recipe_name');
$this->primaryKey('recipe_id');
$this->belongsTo('Recipes', [
'foreignKey' => 'recipe_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Ingredients', [
'className' => 'Ingredients',
'joinTable' => 'ingredients_recipes',
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->requirePresence('recipe_name', 'create')
->notEmpty('recipe_name')
->requirePresence('recipe_description', 'create')
->notEmpty('recipe_description')
->requirePresence('Ingredients', 'create')
->notEmpty('Ingredients');
return $validator;
}
}
IngredientsTable
class IngredientsTable extends Table
{
public function initialize(array $config)
{
$this->table('ingredients');
$this->displayField('ingredient_name');
$this->primaryKey('ingredient_id');
$this->belongsTo('Ingredients', [
'foreignKey' => 'ingredient_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Recipies', [
'className' => 'Recipies',
'joinTable' => 'ingredients_recipes',
'foreignKey' => 'ingredient_id',
'targetForeignKey' => 'recipe_id'
]);
}
}
IngredientsRecipesTable
class IngredientsRecipesTable extends Table
{
public function initialize(array $config)
{
$this->table('ingredients_recipes');
$this->displayField('recipe_id');
$this->primaryKey(['recipe_id', 'ingredient_id']);
$this->belongsTo('Recipies', [
'foreignKey' => 'recipe_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Ingredients', [
'foreignKey' => 'ingredient_id',
'joinType' => 'INNER'
]);
}
Controller:
RecipesController
public function add()
{
$recipe = $this->Recipes->newEntity();
if ($this->request->is('post')) {
$recipe = $this->Recipes->patchEntity($recipe, $this->request->data);
// var_dump($this->request->data);
if ($this->Recipes->save($recipe)){
$this->Flash->success('The recipe has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The recipe could not be saved. Please, try again.');
}
}
$recipes = $this->Recipes->find('list', ['limit' => 200]);
$ingredients = $this->Recipes->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE MESSAGE "The recipe could not be saved. Please, try again."
$ingredients = $this->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE ERROR "Call to a member function find() on boolean"
$this->set(compact('recipe', 'recipes', 'ingredients'));
$this->set('_serialize', ['recipe']);
}
Template > Recipes
add.ctp
<?= $this->Form->create($recipe); ?>
<fieldset>
<legend><?= __('Add Recipe') ?></legend>
<?php
echo $this->Form->input('recipe_name', array(
'label' => 'Name'
)
);
echo $this->Form->input('recipe_description', array(
'label' => 'Description'
)
);
echo $this->Form->input('Recipes.Ingredients', ['multiple'=>true]);
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
Maybe this tutorial could help: http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html.
It explains exactly how to set up HABTM relations, adding tags to bookmarks, and more.
Good luck and happy coding :)