I'm newbie on CakePHP, and now I'm stuck on many to many situation
ok, i have 3 Table :
questions
with fields (id, question)
question_product
with fields (id, question_id, product_id, question_number, is_enabled)
products
with fields (id, name, code, is_enabled)
so when i want to select questions with specific field, i don't know how to fix it
for now, my code is like this :
Question.php (Model)
class Question extends AppModel {
public $hasAndBelongsToMany = array (
'Product' => array (
'joinTable' => 'question_product',
'foreignKey' => 'question_id',
'associationForeignKey' => 'product_id',
'unique' => 'keepExisting',
'order' => 'question_number',
'fields' => array (
'QuestionProduct.question_number',
'Product.id',
'Product.name'
),
'conditions' => array (
'QuestionProduct.is_enabled' => 1,
)
)
);
}
QuestionsController.php (Controller)
public function loadQuestions($productId) {
$this->view = 'load_questions';
$questions = $this->Question->find('all', array (
'fields' => array (
'Question.id',
'Question.question',
'Question.is_optional',
'Question.reason_optional',
'Question.text_size'
),
'conditions' => array (
'QuestionProduct.product_id' => $productId
)
));
$this->set($questions);
}
method loadQuestions have one parameter to select with specified product
if i using sql query, it will be like this
select all from Question with condition Product.product_id=4, sorted by QuestionProduct.question_number ascending
select questions.*
from questions
join question_product on questions.id=question_product.question_id
join products on products.id=question_product.product_id
where products.id=4
order by question_product.question_number;
any answer will be appreciated :)
Thanks !
Any time you use a many-many (HABTM) relation with any other field that requires conditions, it is no longer many-many as far as Cake is concerned. You want the HasManyThrough relationship
Instead of using hasAndBelongsToMany relation, use two belongsTO relation from question_product to questions and another time from question_product to products.
question_product belognsTo questions
question_product belongsTo products
NOTE:you should change the table name from question_product to question_products as cakePHP convention
in your model QuestionProduct model :
<?php
// declares a package for a class
App::uses('AppModel', 'Model');
class QuestionProduct extends AppModel {
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
),
'Question' => array(
'className' => 'Question',
'foreignKey' => 'question_id',
),
);
then in your Controller :
public function loadQuestions($productId) {
$this->view = 'load_questions';
$questions = $this->QuestionProduct->find('all', array (
'fields' => array (
'Question.id',
'Question.question',
'Question.is_optional',
'Question.reason_optional',
'Question.text_size'
),
'conditions' => array (
'QuestionProduct.product_id' => $productId
),
'contain' => array('Product','Question')
));
$this->set($questions);
}
It should make exactly the query you want, and I don't think it has any other way to produce that query.
Related
Will the given below code can work where Mark and Subject are two model with which Student model associate through hasMany association
class Student extends AppModel
{
public $name = 'Student';
var $hasMany = array(
'Mark' => array(
'className' => 'Mark',
'foreignKey' => 'student_id'
),
'Subject' => array(
'className' => 'Subject',
'foreignKey' => 'student_id'
)
);
}
yes you can relationship with many tables
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Find conditions with hasMany model
I'm facing a problem with cakephp associations in Models.
I have to Select records which have atleast one hasMany reation row
Model
class Category extends AppModel
{
public $hasMany = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'CategoryId',
)
);
}
Query
$categories = $this->Category->find('all');
I only needed the categories which have atleast one product entry
Categories Like : Shirts, Footwear, Glasses etc
Products like :
Small, medium, large (Shirts)
With Frame, UV protected (Glass)
So, i jus want to get Shirts and Glasses Categories only because for the above example there is no products for Footwear
Use counterCache or joins
Please refer to CakePHP - Find and count how many associated records exist
The most simple way with the best performance would be using a properly indexed counter cache field as shown in the linked answer.
Sice the linked answer is not an exact duplicate with respect to the join, here's some additional info, instead of using HAVING COUNT with the join you'd use a IS NOT NULL condition. Here's an (untested) example:
$this->Category->find('all', array(
'joins' => array(
array(
'table' => 'products',
'alias' => 'Product',
'type' => 'LEFT',
'conditions' => array('Category.id = Product.CategoryId')
)
),
'conditions' => array(
'Product.CategoryId IS NOT NULL'
)
'group' => 'Category.id'
));
Depending on the used DBMS and version you might get better performance using an inner join:
$this->Category->find('all', array(
'joins' => array(
array(
'table' => 'products',
'alias' => 'Product',
'type' => 'INNER',
'conditions' => array('Category.id = Product.CategoryId')
)
),
'group' => 'Category.id'
));
I have two tables with a relation. The find method works very well without problem, i get all the fields that i want between both tables. The delete method work fine also.
But i want to add a validation before delete the record, verifiying the relation with the secondary table, if the relation exists can't delete the record.
My Tables:
invoices: id, company_id, number, invoice_date
invoice_numbers: id, company_id, number
companies: id, name
My Models:
Invoice:
class Invoice extends AppModel
{
public $belongsTo = array(
'Company',
'InvoiceNumber' => array(
'className' => 'InvoiceNumber',
'foreignKey' => false,
'conditions' => array(
'InvoiceNumber.company_id = Invoice.company_id',
'InvoiceNumber.number = Invoice.number',
),
'type' => 'left',
'fields' => array(
'FacturaNumero.id',
),
)
);
}
InvoiceNumber:
class InvoiceNumber extends AppModel
{
public $hasOne = array(
'Invoice' => array(
'className' => 'Invoice',
'foreignKey' => false,
'conditions' => array(
'Invoice.company_id = InvoiceNumber.company_id',
'Invoice.number = InvoiceNumber.number',
),
'type' => 'left',
'fields' => array(
'Invoice.invoice_date',
),
)
);
}
My Controller (delete Method):
public function delete($id)
{
$this->InvoiceNumber->id = $id;
if (!$this->InvoiceNumber->exists()) {
throw new NotFoundException('Invalid Record.');
}
/**
* I want to add a validation here if field invoide_date is not null, by example
* but this dont work, because get the first value in the invoice table.
*/
if (!empty($this->InvoiceNumber->Invoice->field('invoice_date')) {
throw new NotFoundException('I Cant Delete this record.');
} else {
$this->InvoiceNumber->delete();
}
}
The misunderstanding here is that when you write
$this->InvoiceNumber->Invoice
you are just accesing a generic Invoice Model Object and not the Invoice of that specific InvoiceNumber
I think that you just need to change your code in
$InvoiceNumber = $this->InvoiceNumber->read();
if (!empty($InvoiceNumber['Invoice'])) {
// ...
}
but maybe this is not the best way: I think that a cleaner approach could be to do the validation in the InvoiceNumber beforeDelete() method
PS: sorry for the typos. Now should be correct
I am in the FilesController and I'm trying to get a file based on the condition that its order belongs to the current user.
FilesController
// Check the file exists and that it belongs to the user
$this->File->find('first', array(
'conditions' => array(
'File.id' => $id,
'Order.Customer.id' => $this->Auth->user('id')
),
'recursive' => 2
));
Cake SQL Error
Unknown column 'Order.Customer.id' in 'where clause'
I'm trying to get the SQL to left join orders onto files and then left join customers onto orders, but I can't figure out how to join the customers table, although I'm sure I've done this before. I've tried everything I can think of with the conditions, and using contains.
Here's my model relationships:
Customer Model
class Customer extends AppModel {
public $hasMany = array('Order');
}
Order Model
class Order extends AppModel {
public $belongsTo = array('Customer');
public $hasMany = array('File');
}
File Model
class File extends AppModel {
public $belongsTo = array('Order');
}
Try joining the tables using the 'joins' parameter. Sometimes 'contains' doesn't work and you need to fall back on this.
$this->File->find('first', array(
'conditions' => array(
'File.id' => $id,
'Order.Customer_id' => $this->Auth->user('id')
),
'joins' => array(
array(
'table' => 'orders',
'alias' => 'Order',
'type' => 'LEFT',
'conditions' => array('File.orders_id = Order.id')
),
array(
'table' => 'customers',
'alias' => 'Customer',
'type' => 'LEFT',
'conditions' => array('Customer.orders_id = Order.id')
)
)
));
You may want to use Containable (http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html) as it is the easiest solution. You can not use Order.Customer.id as Cake does not nest conditions like that. Manual joins would also work.
$this->loadModel('Customer');
$customer = $this->Customer->findById($this->Auth->user('id'), array(
'conditions' => array(
'File.id' => $id
),
'recursive' => 2
));
Orders will be accessible as:
pr($customer['Order']);
File will be accessible as:
pr($customer['File']);
I realised there is no need to actually join the customers table in this instance, because the orders table already has the customer_id.
$this->File->find('first', array(
'conditions' => array(
'File.id' => $id,
'Order.customer_id' => $this->Auth->user('id')
)
));
I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.