CakePHP - One HABTM With Two Foreign Keys - php

I've been trying to save two foreign keys to the same row of a join table with no success. My Client model has two HABTM association with both User and Group models, a user can have many clients and create clients under many different groups.
The UserClient join table looks like this:
+----+---------+----------+-----------+
| id | user_id | group_id | client_id |
+----+---------+----------+-----------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 2 |
| 3 | 3 | 4 | 3 |
+----+---------+---------+------------+
Client model:
public $hasAndBelongsToMany = array(
'User' => array(
'className' => 'User',
'joinTable' => 'UserClient',
'foreignKey' => 'client_id',
'associationForeignKey' => 'user_id'
),
'Group' => array(
'className' => 'Group',
'joinTable' => 'UserClient',
'foreignKey' => 'client_id',
'associationForeignKey' => 'group_id'
)
);
Client view:
<?php echo $this->Form->create('Client'); ?>
<fieldset>
<legend><?php echo __('Add Client'); ?></legend>
<?php
echo $this->Form->input('first_name');
echo $this->Form->input('last_name');
echo $this->Form->input('email');
echo $this->Form->input('phone');
echo $this->Form->input('Client', array('multiple'=> 'false', 'options' => $group_ids));
echo $this->Form->hidden('User', array('value' => $user_id));
?>
</fieldset>
<?php echo $this->Form->end(__('Submit')); ?>
This works to a point, however instead of saving user_id, group_id and client_id to the same row in UserClient table, it create two separate rows for each HABTM, user_id, client_id get saved to one row and group_id, client_id get saved to another.
Is there any way to define multiple associationForeignKey within the same HABTM association so user_id, group_id and client_id get saved to the same row when creating a new client?

When you have to store additional fields in a HABTM relation, it is sometimes easier to use a join model. In your case this would mean creating a model for your UserClient table and use hasMany relations instead of HABTM.
Then of course you'll have to manage the save manually, but you'll have much more control of what must be done. You can find a few words about this in the Cake documentation.

Related

BelongsToMany associations in CakePHP 3, how to keep relations and save associations in a secondary field?

I need help to create a belongsToMany relation between 2 tables. I need this relation to be permanent and the "state of the relation" to be stored in an additional field in the relation table.
Here goes some background. I'm buildind a website for a restaurant.
This restaurant has a few cards (Brasserie, Gastronomique...):
cartes
id | name | description
Every card has many sections (Starters, Salad, Meat, Fish...). Every section may happen in multiple cards.
cartes_rubriques
id | name | description | comment
Cards and Sections have a belongsToMany relation thanks to a table
cartes_cartes_rubriques
id | carte_id | rubrique_id| order | description | comment | active (boolean)
I need this relation to be permanent. The relation may be temporarly removed as some sections may be seasonal but I need to keep the order, description and comment to be saved.
I've tried 'saveStrategy' => 'append' but this only adds new records to cartes_cartes_rubriques.
CartesTable.php
$this->belongsToMany('Rubriques', [
'foreignKey' => 'carte_id',
'targetForeignKey' => 'rubrique_id',
'through' => 'cartes_rubriques'
]);
CartesRubriquesTable.php
$this->belongsToMany('Cartes', [
'foreignKey' => 'rubrique_id',
'targetForeignKey' => 'carte_id',
'through' => 'cartes_rubriques'
]);
CartesCartesRubriquesTable.php
$this->belongsTo('Cartes', [
'foreignKey' => 'carte_id',
'joinType' => 'INNER'
]);
$this->belongsTo('CartesRubriques', [
'foreignKey' => 'cartes_rubrique_id',
'joinType' => 'INNER'
]);
My strategy would ideally be to save theses relations and toggle cartes_cartes_rubriques.active.
Does this seem to be a good / possible strategy?
How can I make the toggeling on cartes_cartes_rubriques.active?
Do you have a better option?
Thanks for your help.
Antonio

CakePHP contain() method sees belongsToMany over belongsTo of same table

So I have the following (simplified) model
+------------------+
| project_statuses |
+------------------+
+---------------------+ +----| id |
| projects | | | name |
+---------------------+ | +------------------+
| id | | +---------+
| name | | | clients |
| project_statuses_id |----+ +---------+
| client_id |---------| id |
+---------------------+ | name |
| +---------+
+------------------+ |
| clients_projects |------+
+------------------+
| id |
| client_id |
| project_id |
+------------------+
where a project belongs to many clients and a client can have many projects, but only one client (Projects.client_id) can take the responsibility for a project. The project status is here just for comparision.
So the associations in my ProjectsTable.php, ClientsTabe.php and ProjectStatusesTable.php look like this
// In ProjectsTable.php
$this->belongsTo('ProjectStatuses', [
'foreignKey' => 'project_status_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Clients', [
'foreignKey' => 'project_id',
'targetForeignKey' => 'client_id',
'joinTable' => 'clients_projects'
]);
// In ClientsTabe.php
$this->hasMany('Projects', [
'foreignKey' => 'client_id'
]);
$this->belongsToMany('Projects', [
'foreignKey' => 'client_id',
'targetForeignKey' => 'project_id',
'joinTable' => 'clients_projects'
]);
// In ProjectStatusesTable.php
$this->hasMany('Projects', [
'foreignKey' => 'project_status_id'
]);
Now in my projects/index page I'd like to have a table showing the project id, name, status and responsible. So I thought in something like this
// In ProjectsController.php
$this->Projects->find()->select(['Projects.id','Projects.name'])
->contain(['ProjectStatuses' => [
'fields' => [
'ProjectStatuses.name',
]]])
->contain(['Clients' => [
'fields' => [
'Clients.name',
]]]);
But only ProjectStatuses.name is fetched, for Clients.name it throws the error You are required to select the "ClientsProjects.project_id" field(s) telling me that is looking through the belongsToMany association rather than the belongsTo one.
In fact, if I just write ->contain('Clients') instead of specifying the Clients.name field it send the following queries
SELECT
Projects.id AS `Projects__id`,
Projects.name AS `Projects__name`,
ProjectStatuses.name AS `ProjectStatuses__name`
FROM
projects Projects
INNER JOIN project_statuses ProjectStatuses ON ProjectStatuses.id = (Projects.project_status_id)
SELECT
ClientsProjects.id AS `Clients_CJoin__id`,
ClientsProjects.client_id AS `Clients_CJoin__client_id`,
ClientsProjects.project_id AS `Clients_CJoin__project_id`,
Clients.id AS `Clients__id`,
Clients.name AS `Clients__name`,
FROM
clients Clients
INNER JOIN clients_projects ClientsProjects ON Clients.id = (ClientsProjects.client_id)
WHERE
ClientsProjects.project_id in (4)
How can I tell the query object to get the Clients.name through Projects.client_id just as I get ProjectStatuses.name through Projects.project_statuses_id instead of passing by the ClientProjectstable?
In your Projects table, you have created two associations for Clients, and vice versa. That doesn't work (as The Highlander said, "There can be only one"), and is likely the source of all your problems.
You'll need to change one of those associations in each table. Maybe a Project should belongsToMany Clients but only belongTo one ResponsibleClient, and a Client should belongsToMany Projects and hasMany ReponsibleProjects?

CakePhp 3 - link and save associations with a table which holds two foreign keys of same model

i have the following two tables:
recipes
similiar_recipes
As you can see similar_recipes has two foreign keys which both point to recipes. Now I want two things. First of the linking. I read on stackoverflow some similar stuff and come up with the following configuration:
RecipesTable.php
$this->hasMany('Recipes', [
'foreignKey' => 'recipe_id',
'className' => 'SimilarRecipes'
]);
$this->hasMany('SimilarRecipes', [
'foreignKey' => 'similar_recipe_id',
'className' => 'SimilarRecipes'
]);
SimilarRecipesTable.php
$this->belongsTo('Recipes', [
'foreignKey' => 'recipe_id',
'joinType' => 'INNER',
'className' => 'Recipes'
]);
$this->belongsTo('SimilarRecipes', [
'foreignKey' => 'similar_recipe_id',
'joinType' => 'INNER',
'className' => 'Recipes'
]);
The configuration should be correct. Now the other question is the correct associated saving or lets ask better is it possible to do the following:
Recipes Data
Now in Cake I want to add a recipe and the associated recipes which are delivered in the request->data as an id-array
$newRecipe = $this->Recipes->newEntity();
$newRecipe = $this->Recipes->patchEntity($newRecipe, $this->request->data);
$this->Recipes->save($newRecipe, ['associated' => ['SimilarRecipes']])
This should be the result:
In conclusion I saved a new recipe which gets the id 3. In the request->data I select the similar recipes 1 and 2.
Could someone give me an advice. Is my configuration wrong. Or what do I have to pass to the save method? By the way I don't get any errors.
Use belongsToMany associations instead
I'd say your association approach is wrong (or at least makes things unnecessarily complicated), I'd suggest to use belongsToMany associations instead, as what you seem to create there is a self-joining many-to-many relation.
Name the table recipes_similar_recipes, that's the convention CakePHP uses, it helps to avoid association name conflicts/confusion, and allows relying on magic configuration. Your tables associations/configuration should then look something like:
RecipesTable.php
$this->belongsToMany('SimilarRecipes', [
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'similar_recipe_id',
'joinTable' => 'recipes_similar_recipes'
]);
SimilarRecipesTable.php
$this->table('recipes');
$this->primaryKey('id');
$this->belongsToMany('Recipes', [
'foreignKey' => 'similar_recipe_id',
'targetForeignKey' => 'recipe_id',
'joinTable' => 'recipes_similar_recipes'
]);
With such a setup you could then use the array of IDs variant, eg use the _ids key to define the existing (similar) recipies that should be associated with your new recipe, ie the request data should look something like:
[
// there is no title column in your example,
// this should just clarify the data structure
'title' => 'New recipe that will get the ID 3',
'similar_recipes' => [
'_ids' => [
1, 2
]
]
]
which should populate the recipes_similar_recipes table like:
+----+-----------+-------------------+
| id | recipe_id | similar_recipe_id |
+----+-----------+-------------------+
| 1 | 3 | 1 |
| 2 | 3 | 2 |
+----+-----------+-------------------+
You should then also consider making recipe_id and similar_recipe_id a compound primary key, or at least create a unique index with them.
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together > BelongsToMany Associations
Cookbook > Database Access & ORM > Saving Data > Converting BelongsToMany Data

Yii join two models

I've got two mysql tables:
1. prices
2. details
Table Prices
ID | DATE | CODE | PRICE
1 |2014-01-01 | AAA1 | 90.123
2 |2014-01-01 | AAB1 | 50.113
3 |2014-01-01 | AAC1 | 48.621
4 |2014-01-02 | AAA1 | 91.123
5 |2014-01-02 | AAB1 | 51.113
6 |2014-01-02 | AAC1 | 41.621
Table Details
CODE | NAME | DESCRIPTION
AAA1 | andria | A very good...
AAB1 | anasta | A very good...
AAC1 | simple | A very good...
Models:
Prices
public function relations(){
return array(
'code' => array(self::BELONGS_TO, 'details', 'code'),
);
}
Details
public function relations(){
return array(
'code' => array(self::HAS_MANY, 'prices', 'code'),
);
}
Here's the SQL code I want to execute:
SELECT * FROM prices a
JOIN (SELECT * FROM details) b
WHERE a.DATE=(SELECT MAX(DATE) FROM prices) AND a.code = b.code
My controllers:
$prices=new CActiveDataProvider('prices', array(
'criteria'=>new CDbCriteria (array(
'select'=>'code,date,close',
'condition'=>'date=(SELECT MAX(date) FROM prices)'
)),
));
$this->render('index',array(
'prices'=>$prices
));
index.php:
<?php
$this->widget('bootstrap.widgets.TbGridView', array(
'dataProvider'=>$prices,
'template'=>"{items}",
'enablePagination' => false,
'columns'=>array(
array('name'=>'code', 'header'=>'Code'),
array('name'=>'name', 'header'=>'Name'),
array('name'=>'close', 'header'=>'Close'),
),
)); ?>
With this controller commands, I can get the data from table prices, but I can't get the data from table details.
Take a look at the with part of the criteria: http://www.yiiframework.com/doc/api/1.1/CDbCriteria#with-detail
I think your code should look something like this (untested):
$prices=new CActiveDataProvider('prices', array(
'criteria'=>new CDbCriteria (array(
'select'=>'code,date,close',
'with' => 'details',
'condition'=>'date=(SELECT MAX(date) FROM prices)'
)),
));
you already have reliation in Model. So do query like this in your controller
$prices=new CActiveDataProvider('prices', array(
'criteria'=>new CDbCriteria (array(
'condition'=>'date=(SELECT MAX(date) FROM prices)'
)),
));
In View just use
$data->code->name
$data->code->description
Here's what I change:
index.php
<?php
$this->widget('bootstrap.widgets.TbGridView', array(
'dataProvider'=>$prices,
'template'=>"{items}",
'enablePagination' => false,
'columns'=>array(
array('name'=>'code', 'header'=>'Code'),
array('name'=>'name', 'header'=>'Name', 'value'=>'$data->details->name'),
array('name'=>'close', 'header'=>'Close'),
),
)); ?>
Models Prices
public function relations(){
return array(
'details' => array(self::BELONGS_TO, 'Stocks_details', 'code'),
);
}

How should I link these models to query multi tables via CakePHP associations?

I am using cakephp 2.3.2 and I need to do a query on multi tables.
I have this database:
--------------------------------------------------------------
| Users | Agents | Companies | Ads |
--------------------------------------------------------------
| id | id | id | id |
| username | name | company | title |
| password | lastname | sector | message |
| | user_id | user_id | user_id |
--------------------------------------------------------------
These are the associations (Models):
User
hasOne Agent
hasOne Company
hasMany Ads
Agent
belongsTo User
Company
belongsTo User
Ad
belongsTo User
(NOTE: Please, keep in mind that when I add a new user that user could be an Agent OR a Company.)
QUESTION:
In my AdsController I have an action named view, there I read two params that I receve from Route:
$this->params['id']
$this->params['sector']
I need to do a query to check if the id is really associated to an Ad.id and If the sector is associated to Company.sector
I would like to check it with ONE find('first') instead of checking
If the ID exists
If the sector exists and it is associated to the user_id
How could I do it ?
(If the query finds Ad.id and Company.sector I need to retrieve all fields of Ad and Company)
At the moment my find('first') in AdsController/view is:
$options = array(
'fields' => array(
'Ad.*'
),
'contain' => array(
'User' => array(
'Company' => array(
'fields' => array(
'Company.*'
),
'conditions' => array(
'Company.sector' => $this->params['sector'],
),
)
)
),
'conditions' => array(
'Ad.id' => $this->params['id'],
)
)
$data = $this->Ad->find('first', $options);
debug($data);
The problem is that Company is now shown in the result (array).
Please, keep in mind that I only need to retrieve the array IF:
The ID of the AD exists
The sector of the Company exists
If one of above are not "true" I would like to get an empty array.
Obviously I have added Containable behavior in Ad model.
I've seen this, had same problem that for some reason contain, kinda malfunctions with that array() structure, maybe it works how it means to be, but I dont get it, anyways to retrive chain dependencies i use this kind of construction:
$options = array(
'contain' => array(
'User',
'User.Company' => array(
'conditions' => array(
'Company.sector' => $this->params['sector'],
),
)
),
'conditions' => array(
'Ad.id' => $this->params['id'],
)
)
But, you need to have in mind that conditions in contain are not the same as the conditions for main model, therfore even if the company sector does not exist but Ad with given id exits you will not receive an empty set. As far as I know you cannot use a condition to exclude results if there is no direct association between models. So you would still need to check if $result['User']['Company'] is an empty set or not.
One more thing your query array does not match structure you've provided with your question, there is no field sector in your Company table

Categories