CakePHP: Multiple links to same model - php

Lets say each book has AuthorA and AuthorB fields.
Both fields are foreign keys for authors table.
table authors with fields: id | name
table books with fields: id | name | a_author_id | b_author_id |
How should controller, model and view be set up to be able to create a book with dropdown list of "author a" and "author b" both coming from author table? How would form-input for author-a (and author-b) look like inside "Add" view for Book model?

You need to setup 2 belongsTo association in your Book model with different aliases for Author model.
public $belongsTo = array(
'AAuthor' => array(
'className' => 'Author',
'foreignKey' => 'a_author_id'
),
'BAuthor' => array(
'className' => 'Author',
'foreignKey' => 'b_author_id'
)
);
Use $this->Book->AAuthor->find('list') to get the authors list and set the array to view and specify same array using 'options' key in Form->input() options for both a_author_id and b_author_id fields.

Related

CakePHP 3: belongsToMany (through) and additional associations

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']])

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

How to retrieve data from two tables?

I have two tables
posts
------
post_id | user_id | post_title | post_content
And
users
--------
id | user_name | user_ . .. . . . and so on
I need to fetch all posts with the user data
to show the post writer etc..
How can I achieve this using CakePHP queries?
In your UserModel you define:
var $hasMany = 'Post';
In your PostModel you define:
var $belongsTo = 'User';
Then you can get all post of some user doing:
$this->User->findAllById($id, array('recursive' => 2));
Or you can get ALL posts associated with the respective users doing:
$this->Post->find("all", array('recursive' => 2));
EDIT:
Your Posts id column is named post_id so you must define primary key in your PostModel since CakePHP conventions is that primary key should be named id:
public $primaryKey = 'post_id';
$this->Post->find('all',array('fields'=>array('User.*'),'conditions'=>array('Post.user_id=User.id')),
joins' => array(
array(
'alias' => 'User',
'table' => 'users',
'type' => 'Inner',
'conditions' => array('User.user_id' =>$id)
)
)
));

Model Associations and Data Modelling

I am developing a web app for an art gallery, and I want to check I have set up the data model correctly.
I have a people table and an artists table. Some of the people are artists and some are not.
I also have a products table and each product belongs to one artist.
Below is a simplified version of the tables:
products
********
id
title
artist_id
artists
*******
id
profile
rating
person_id
people
******
id
first_name
last_name
I need to be able to retrieve the name of the artist when performing the find() method on the Product model.
Have I set up the data model correctly and if so what is the best way to retrieve the artist's name without getting lots of unwanted data?
It's possible to use CakePHP's bindModel & unbindModel to build up relations that don't rely on Cake's automagic goodness.
Since you were interested in doing the find on the Product model, I'll set out an example that can be called from the Products controller:
// unbind Artist
$this->Product->unbindModel(array(
'belongsTo' => array('Artist')
));
// bind Artist & Person using explicit conditions
$this->Product->bindModel(array(
'hasOne' => array(
'Artist' => array(
'foreignKey' => false,
'conditions' => array('Artist.id = Product.artist_id')
),
'Person' => array(
'foreignKey' => false,
'conditions' => array('Person.id = Artist.person_id')
),
)
));
// return the person
$person = $this->Product->find('first', array(
'conditions' => array(
'Product.id' => 1 // or any other condition
),
'fields' => array(
'Person.first_name',
'Person.last_name'
)
));
What's happening here?
Firstly, we unbind the Product model's relationship with the Artist model.
Secondly, we bind the Artist and Person models by explicitly defining the relationships and disabling Cake's automatic forigenKey connections.
Lastly, we do a 'first' find on the Product model and only request the 'first_name' & 'last_name' of the Person.
Each 'Product' belongs to an 'Artist' and each 'Artist' hasAndBelongsToMany 'Product'
You will also need the following table:
artists_products
****************
id
product_id
artist_id
http://book.cakephp.org/#!/view/1044/hasAndBelongsToMany-HABTM

How to Retrieve fields from linked Models

I have the following three database tables:
Products
########
id
title
artist_id
Arists
######
id
profile
person_id
People
######
id
first_name
last_name
In my Product model how do I create a method to return the product title along with the artist's first_name?
I have set up the following model associations:
Product belongs to Artist
Artist belongs to Person
Containable is definitely the way to go for filtering related records. Make sure to add $actsAs = array('Containable') into your model or app_model.
Then you can do things like:
$this->Product->find('all', array(
'contain' => array(
'Artist' => array(
'Person' => array(
'id',
'first_name'
)
)
)
));
Assuming you already set the relationships in these models, you just need to set it recursive:
$this->Product->recursive = 2;
print_r($this->Product->find('all'));

Categories