Getting value of a field in a nesting relation - cakephp

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

Related

CakePHP update a child model from parent

I am currently struggling to get an associated model to update with CakePHP 2.3. I want the parent model to be inactive, and the child model record to be inactive. I have the following method in my Company Model, which just updates the parent model (and not the child). What is the best way to achieve this? I tried moving the method to the child object, but it makes no difference.
class Company extends AppModel {
public $hasMany = array(
'CompaniesUser' => array(
'className' => 'CompaniesUser'
)
);
function updateToInactive( $companyId ){
$data = array(
array(
'Company' => array(
'id' =>$companyId,
'active' => 0,
'payment_active'=> 0
),
'CompaniesUser' => array(
'company_id' =>$companyId,
'active' => 0)
)
);
$this->saveAll($data);
}
}
My CompaniesUser Model looks like this:( belongs to Company )
class CompaniesUser extends AppModel {
public $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id'
)
);
Your $data array looks wrong to me for a hasMany relationship. It should look something more like this:-
$data = array(
'Company' => array(
'id' =>$companyId,
'active' => 0,
'payment_active'=> 0
),
'CompaniesUser' => array(
array(
'company_id' =>$companyId,
'active' => 0
)
)
);
$this->saveAssociated($data);
It is also better to use saveAssociated() than saveAll() as you are saving associated data.
Update
The above will not update the existing CompaniesUsers unless you pass the primary keys with the save data. You could save the company first then use updateAll() to update the users data for the related company. If you use updateAll() you need to remember to escape the values being saved:-
// Update company
$data = array(
'id' =>$companyId,
'active' => 0,
'payment_active'=> 0
);
$this->save($data);
// Update company users
$this->CompaniesUser->updateAll(
array('CompaniesUser.active' => '"0"'),
array('CompaniesUser.company_id' => '"' . $companyId . '"')
);

cakephp saveAll associated model field is not being saved

I'm currently using cakephp 2.2.3.
I have the following Model Associations:
VehicleModel -> Vehicle -> Order
Plan -> Order
Vehicle HABTM Tag
Inside the Vehicle controller, add action, I have:
if(!empty($this->request->data)) {
if($this->Vehicle->saveAll($this->request->data)) {
$this->Session->setFlash('Vehicle was successfully added.');
}
}
The $this->request->data array is formatted like this:
array(
'VehicleModel' => array(
'category_id' => '2',
'make_id' => '1'
),
'Order' => array(
'plan_id' => '2'
),
'Vehicle' => array(
'vehicle_model_id' => '13',
'price' => ' 8700',
'year' => '1994',
'km' => '100',
'color' => '61',
'fuel' => '1',
'gear' => '20',
'type' => '51',
'city' => 'Rio de Janeiro',
'state' => 'RJ'
),
'Tag' => array(
'Tag' => array(
(int) 0 => '69',
(int) 1 => '11'
)
)
)
The orders table has the following fields:
id , plan_id , vehicle_id , created , modified.
Vehicle Model:
class Vehicle extends AppModel {
public $belongsTo = array('User' , 'VehicleModel');
public $hasMany = array('Order' , 'Image');
public $hasAndBelongsToMany = array('Accessory' , 'Tag');
}
Order Model:
class Order extends AppModel {
public $belongsTo = array('Vehicle' , 'Part' , 'Plan');
public $validate = array(
'plan_id' => array(
'rule' => 'notEmpty'
)
);
}
The problem I'm having is that the Order.plan_id field is not being saved, although all other fields are being saved normally. What can I be doing wrong?
Just to be clear, When I do the multiple saving manually, everything
works just fine. I mean, when I write:
$this->Vehicle->save()
and then set
$this->request->data['Order']['vehicle_id'] = $this->Vehicle->id
and finally
$this->Vehicle->Order->save()
everything works just fine. It's the saveAll that is causing me
trouble.
If that is the case, see where $this->request->data['Order']['vehicle_id'] = $this->Vehicle->id. Comparing this to your var dump above, order never contains the relation to the main model, which leads me to ask if this is a new record you are trying to save or an update? I think you might have to not go with a saveAll here if you are setting a new record because the main id is not yet set. Please see:
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-hasone-hasmany-belongsto
Particularly: "If neither of the associated model records exists in the system yet (for example, you want to save a new User and their related Profile records at the same time), you’ll need to first save the primary, or parent model."
They basically do the long version you are doing.

CakePHP Many To Many With Conditions

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.

CakePHP - How do I join a table on a joined table?

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

HABTM Find with CakePHP 2.0

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.

Resources