I have defined the following associations:
class RecipesTable extends Table
{
$this->belongsToMany('Ingredients', [
'through' => 'RecipesIngredients',
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
]);
class IngredientsTable extends Table
{
$this->belongsToMany('Recipes', [
'through' => 'RecipesIngredients',
'foreignKey' => 'ingredient_id',
'targetForeignKey' => 'recipe_id',
]);
class RecipesIngredientsTable extends Table
{
$this->belongsTo('Recipes');
$this->belongsTo('Ingredients');
$this->belongsTo('Units');
The table 'RecipesIngredients' has the following structure:
id | recipe_id | ingredient_id | unit_id | ...
Now I make a request like the one below to get Recipes and the associated Ingredients. But without the Units.
$data = $this->Recipe->find('all')
->where('Recipe.id' => 55)
->contain(['Ingredient', ...])
->all();
My question is: how do I get the data of the associated 'Units' in a call of $this->Recipe?
I tried different contains like ->contain(['Ingredient' => ['Unit'], ...]) (and so on) but this doesn't work. CakePHP just returns the associated ingredients and the contents of the 'through' join table without linking to the associated units. Or gives an error of missing associations.
That won't work using contain(), at least not with a belongsToMany association, as the on-the-fly created intermediate association for the join table is being created too late for the eager loader to recognize it.
What you can do is explicitly create the otherwise on-the-fly generated hasMany association for the join table manually, eg on the RecipesTable class add:
$this->hasMany('RecipesIngredients', [
'foreignKey' => 'recipe_id'
]);
Then you can contain your associations like:
->contain(['RecipesIngredients' => ['Ingredients', 'Units']])
Related
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
My application allows a user to create scenarios by linking together soe_blocks. In turn, soe_blocks refer to a variable number of soe_entries.
To build scenarios, soe_blocks are linked to the scenario and ordered by an offset. The soe_blocks can be used in many different scenarios. soe_entries can relate only to a single soe_block
I think the relationship is defined as:
scenarios belongsToMany soe_blocks through scenarios_soe_blocks
soe_blocks belongsToMany scenarios through scenarios_soe_blocks
scenarios_soe_blocks is where the offset is kept
soe_entries haveOne soe_blocks
Tables:
scenarios: id | name
data: 0, 'scenario_1'
soe_blocks: id | name
data: 0, 'soe_block_1'
1, 'soe_block_2'
scenarios_soe_blocks: id | scenario_id | soe_block_id | offset
data: 1, 0, 1, 1
2, 0, 2, 2
Models:
class ScenariosTable extends Table
{
$this->belongsToMany('SoeBlocks', [
'foreignKey' => 'scenario_id',
'targetForeignKey' => 'soe_block_id',
'through' => 'ScenariosSoeBlocks',
'joinTable' => 'soe_blocks'
]);
}
class SoeBlocksTable extends Table
{
$this->belongsToMany('Scenarios', [
'foreignKey' => 'soe_block_id',
'targetForeignKey' => 'scenario_id',
'joinTable' => 'scenarios_soe_blocks',
'through' => 'ScenariosSoeBlocks'
]);
}
class ScenariosSoeBlocksTable extends Table
$this->belongsTo('SoeBlocks', [
'foreignKey' => 'soe_block_id',
'joinType' => 'INNER'
]);
}
Controllers:
public function view($id = null)
{
$scenario = $this->Scenarios->get($id, [
'contain' => ['SoeBlocks', 'RunStatus', 'ScenarioLog']
]);
$this->set('scenario', $scenario);
}
As far as I can make out from CakePHP Doc, this is all I need. But I couldn't get the ScenarioController->view() method to return the offsets from the scenarios_soe_blocks table associated with the soe_blocks.
I tried to add ScenariosSoeBlocks into the 'contain' clause in the ScenarioController, but got the error: Scenarios is not associated with ScenariosSoeBlocks. I found an SO article that suggested I add the following to the ScenarioTable:
$this->hasMany('ScenariosSoeBlocks', [
'foreignKey' => 'scenario_id'
]);
This seems to have worked, and now I can request ScenariosSoeBlocks in my controller like this:
$scenario = $this->Scenarios->get($id, [
'contain' => ['SoeBlocks', 'ScenariosSoeBlocks', 'RunStatus', 'ScenarioLog']
]);
Which at least gets the data into the view template, but not in the single object I'm hoping for. Eventually, I want to be able to CRUD the soe_blocks along with their associated soe_entries, in an object that looks like this:
offset | soe_block_id | soe_entry_id |
I have many other questions, like how to save etc., but I figured I need to get this working first.
So, my questions for now are:
are my associations correct?
how do I retrieve all the associations to view?
are my associations correct?
The first two are, but then it should be:
soe_blocks hasOne soe_entries
soe_entries belongsTo soe_blocks
how do I retrieve all the associations to view?
By containing them, just like you did in your first example. This question seems to originate from the question how to access the join table data, which is very simple, the join table data is being set on the target table entity (Scenario or SoeBlock, depending on from which side/table you issue the query), in a property named _joinData:
$joinTableEntity = $scenario->soe_blocks[0]->_joinData;
$offset = $joinTableEntity->offset;
You can easily gather information about the data structure by dumping your entity contents:
debug($scenario);
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together
Cookbook > Database Access & ORM > Saving Data > Saving Additional Data to the Join Table
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
I have a table in my DB that has a association with itself. I am trying to get all parent categories with their child categories, but I can't get it to work.
This is how the table looks:
id | name | description | image | is_child | forum_category_id | level
Now, obviously the "forum_category_id" is the parent id that refers to the same table.
I baked the model and this is in the table file:
$this->belongsTo('ForumCategories', [
'foreignKey' => 'forum_category_id'
]);
$this->hasMany('ForumCategories', [
'foreignKey' => 'forum_category_id'
]);
The code I use to load from DB is this:
debug($results = $this->find()
->order(['id' => 'ASC'])
->where(['is_child' => 0])
->toArray()
);
With this code, I do get the parent categories, but not the children.
So I thought to use "Contain", but that only returns empty parent categories.
debug($results = $this->find()
->order(['id' => 'ASC'])
->where(['is_child' => 0])
->contain([
'ForumCategories' => function ($q)
{
return $q
->where(['is_child' => 1]);
}
])
->toArray()
);
I have no idea how to get the child categories. I read something about using "Threaded" (no results/success so far) or the TreeBehaviour, but I don't really any idea on how to use them.
Any idea on how this is still possible is much appreciated!
You should use different aliases for the 2 association.
$this->belongsTo('ForumCategories', [
'foreignKey' => 'forum_category_id'
]);
$this->hasMany('ForumChildCategories', [
'className' => 'ForumCategories',
'foreignKey' => 'forum_category_id'
]);
By this $this->ForumCategories->find() will give you the parent and $this->ForumChilfCategories->find() the children.
Otherwise - if that is option - change your database schema and use tree behaviour.
I have 3 tables in the following format.
users
id
FirstName
LastName
userjobs
id
jobinfo
starredjobs
id
user_id
userjob_id
comments
enddate
The 'starredjobs' table holds all the jobs which an user starred/added to favorites.
I have defined the following relationships in their respective model files.
user.php
protected $_has_many = array( 'starredjobs' => array('model' => 'starredjobs' , 'foreign_key'=>'user_id'),
starredjob.php
protected $_belongs_to = array('user' => array('model' => 'user','foreign_key' => 'user_id'));
protected $_has_many = array('jobs' => array('model'=> 'userjob', 'foreign_key'=> 'job_id'));
userjobs.php
none
The idea is to retrieve all the starred jobs and details regarding jobs from the user object. A user can 'n' number of jobs and A job can be starred by 'n' number of users.
Am i defining relationships correctly?
Short answer: No. What you have here is a typical n:m relationship which can easily be used in Kohana using has_many "through" (as is used in the default roles users-relationship). But this doesn't allow for extra attributes in the "middle" table, so you need to use 2 has_many with corresponding belongs_to.
This can be described in plain English like so:
One user has many starredjobs.
One job has many starredjobs.
One starredjob belongs to one user and one job
Also consider the difference between far_key and foreign_key (official doc sadly doesn't cover it), but one easy rule to remember: The key in the other table is far away -> it is the far_key.
This would give you the following
user.php
$_has_many = array(
'starredjobs' => array(
'model' => 'Starredjob',
'far_key' => 'user_id'
)
);
userjob.php
$_has_many = array(
'starredbyuser' => array(
'model' => 'Starredjob',
'far_key' => 'userjob_id'
)
);
starredjob.php
$_belongs_to = array(
'user' => array(
'model' => 'User',
'foreign_key' => 'user_id'
),
'job' => array(
'model' => 'Userjob',
'foreign_key' => 'userjob_id'
)
);
Now you can do various things such as:
//get all jobs starred by given $user
foreach ($user->starredjobs->find_all() as $starredjob) {
//info on userjob via $starredjob->job->jobinfo, etc.
//info from pivot table via $starredjob->comments, etc.
}
//get all users that starred a given $userjob
foreach ($userjob->starredbyuser->find_all() as $starredjob) {
//info on user via $starredjob->user->FirstName, etc.
//info from pivot table via $starredjob->comments, etc.
}