Containable not working correctly with deep association - php

I have a Produto(Product) model that belongsTo Titulo(Title).
Pedido(Order) hasMany Itens and the Itens belongsTo Produto(Product).
however, after I use find(all) using the model Pedido containing Itens with Produto, the next find of Produto, the contain nor the recursive function works, here is an example:
$proModel = ClassRegistry::Init('Produto');
pr($proModel->find('first', array('conditions' => array('pro_cod' => 650), 'contain' => 'Titulo')));
$cart = $pedModel->find('first', array(
'conditions' => array('ped_cod' => $cart['Pedido']['ped_cod']),
'contain' => array(
'Itens' => array(
'Produto' => array(
'Estadia' => array('Atributo', 'Numero'), 'Produtoplataformas'
),
'Troca', 'Locado', 'Tipo',
'Locacao' => array('Plano', 'Itens')
),
'Usuario'
),
'recursive' => -1
));
pr($proModel->find('first', array('conditions' => array('pro_cod' => 650), 'contain' => 'Titulo')));
exit;
The first pr of $proModel->find works ok and prints the 'Titulo' that is associated, however, after the $pedModel->find, the very same $proModel->find doesn't print the 'Titulo' association.
I have no afterFind method.
EDIT:
I pr($proModel->belongsTo) and it changes after $pedModel->find. All associations vanish except the ones used in the contain section of $pedModel->find.
If I use a new Produto instead of ClassRegistry::Init('Produto'), it worked fine, but I guess this is not a good thing, right?
This sucks.

It is a bug of CakePHP:
https://github.com/cakephp/cakephp/issues/5992
You can't have same aliases inside the contain block.
One of the staff said it was a limitation, for me, it is a bug, since it, at least, didn't throw an error.

Related

Complex ordering in CakePHP using Containable

I`m having a problem with the Containable behaviour.
I would like to know if there is any way to access the contained model attributes for operations like ordering.
For example, I have a model B which belongs to a Model A. I need to order objects of B using an attribute (integer) of A. It would be something like:
'contain' => array(
'A' => array(
'B' => array(
'order' => 'A.integer_attribute'
)
)
)
I know that there are easier ways to do this without Containable, but for reasons which are not worth being detailed here, I need to use it. This is an abstract example, in truth model A belongs to other models and this is just a small part of a deep containable tree.
I'd be very glad with any help!
EDIT
OK, I'll try my best to describe the situation without being unclear:
I have 4 models: User, Category, Field and UserField, and their relationships are as follows:
Category hasMany User
Category hasMany Field
User hasMany UserField
Field hasMany UserField
The opposite of these relations are all belongsTo. The purpose here is that the user belongs to a category, which has many fields that he needs to fill with his information (city, state etc). The information he fills is stored in the UserField table, as each information needs to have its field and the user who provided it.
That said, I need to build a screen which displays, for each category, a list of users and their information. So, I retrieve all the categories and its fields, so I can build a table for each category. Each field has an attribute "no_order", which is the number that indicates the order in which the field appears.
At the same time, I need all of each category's users to display them correctly in the tables. Finally, and there's the problem, I need to have UserField objects ordered by the "no_order" of their respective fields, for each user. So I ended up with something like:
$categories = $this->Category->find('all', array(
'order' => 'Category.name',
'contain' => array(
'Field',
'User' => array(
'UserField' => array(
'order' => 'Field.no_order'
)
)
)
));
But this doesn't work, since UserField cannot reach its respective Field's no_order from there.
I apologize if this wasn't clear enough, but for anyone who would spend a little while reading this, I would be VERY grateful for your help!
I am not shure for what do you want so, but I thing that should use joins of cakephp
$A = $this->A->find('all', array(
'joins' => array(
array(
'table' => 'B',
'alias' => 'B',
'type' => 'LEFT',
'conditions' => array(
'B.field_id = A.id'
)
)
),
'conditions' => array(
'B.field' => 'if_you_want_more_conditions'
),
'contain' => array(
'A' => array(
'B' => array(
'order' => 'A.integer_attribute'
)
)
),
'fields' => array('A.field','B.field'),
'recursive' => -1
));
I finally came up with a solution. I don't know how efficient it is, but there it goes:
'contain' => array(
'Field',
'User' => array(
'User.privilege = "Solicitante"'
'UserField' => array(
'order' => '(SELECT f.no_order FROM fields AS f WHERE UserField.field_id = f.id LIMIT 1)'
)
)
)
Having the raw query solved my problem. Hope it helps anybody who comes across a similar problem!

Mamp extremely slow with CakePHP

I have a Cake Application v2.3.1 running locally on mamp v2.1.3 (not pro so this is the latest version) from myapp.dev:port and I'm experiencing some really slow response when I have pagination on the site (about 5 seconds), here's my code in the controller:
public $paginate = array(
'fields' => array('Artist.id, Artist.year_born, Artist.year_died, Artist.country_born'),
'limit' => 50,
'order' => array('Artist.id' => 'desc')
);
I use the twitter bootstrap plugin for my pagination https://github.com/slywalker/TwitterBootstrap like this in AppController.php:
public $helpers = array('Paginator' => array('className' => 'TwitterBootstrap.BootstrapPaginator'));
I tried to set the limit of the pagination to 1 and also tried using cakes own pagination instead of bootstrap, but it's still really slow. I has to be something with the pagination because I can access an artist like myapp.dev:port/artists/view/14532 and that works good so I don't think it has anything to do with the sql query.
I tried around changing names in the /etc/hosts file 127.0.0.1 localhost myapp.dev and ::1 localhost myapp.dev but nothing seems to work. Any ideas? I'm really stuck.
Update: Feel a bit bad about not mentioning the hasMany relationship that my Artist table have. This is my Artist model:
class Artist extends AppModel {
public $hasMany = array(
'ArtistBiography' => array('dependent' => true),
'ArtistSurname' => array('dependent' => true),
'ArtistSignature' => array('dependent' => true),
'ArtistForename' => array('dependent' => true),
'ArtistMonogram' => array('dependent' => true));
public $hasOne = array(
'ArtistActive' => array('dependent' => true));
}
I used the DebugKit and I found that the paginator does 2 unnecessary querys for two left joins that both takes around 2000 ms each to execute. How can I tell the paginator to ignore this? I tried something like this to give a new relationship:
public $paginate = array(
'fields' => array('Artist.id, Artist.year_born, Artist.year_died, Artist.country_born'),
'limit' => 50,
'order' => array('Artist.id' => 'desc'),
'joins' => array(
array('table' => 'artist_forenames', 'alias' => 'ArtistForename', 'type' `=> 'inner', 'conditions' => array('Artist.id = ArtistForename.artist_id')),`
array('table' => 'artist_surnames', 'alias' => 'ArtistSurname', 'type' => 'INNER', 'conditions' => array('Artist.id = ArtistSurname.artist_id'))),
'recursive' => -1
);
But I'm not sure how to make this work. What I want basically is to join the artist_surnames and artist_fornames and ignore two tables called artist.actives and artist_monogram that both together slows down the query with 4000ms.
My second question here is why do these two joins slows it down so much? I did a test-view called artists/all where I'm listing 100 artists and there my query with all the joins is done in around 40-50ms.
TLDR:
Set $recursive to -1
Use CakePHP's Containable Behavior to only retrieve the associated data you want
Example of code:
//wherever you set this variable
$paginate = array(
'recursive' => -1,
'limit' => 50,
'order' => array('Artist.id' => 'desc'),
'contain' => array(
'ArtistSurname',
'ArtistForName'
)
);
Remember to look through the documentation for Containable - it explains how you set your Model(s) to $actsAs Containable, set recursive to -1...etc etc.
I suggest actually setting public $recursive=-1' and public $actsAs =array('Containable'); both in your AppModel - that way ALL models are set up and ready to go with Containable whenever you want. Also, anything higher than -1 for recursive is bad IMHO - Containable is so much better in every respect.

Cakephp loadModel bindmodel not working

I have hasMany Through table which is Chats table with Chat model and I'm using loadModel in User controller to load Chat model then ran below query to bindModel with Chat.user_id and User.id :
$this->loadModel('Chat');
$this->Chat->bindModel(array(
'belongsTo' => array(
'User' => array(
'foreignKey' => false,
'conditions' => array('Chat.user_id = User.id')
)
)
));
$lastChat = $this->Chat->find('all', array(
'conditions' => array(
'Chat.receiver_id' => $user_id['User']['id']
),
'order' => array('Chat.id DESC'),
'fields' => array(
'Chat.id',
'Chat.chat',
'Chat.user_id',
'Chat.receiver_id',
'Chat.read',
'Chat.created'
),
'group' => array('Chat.user_id')
));
I want to join those tables together but this does not seem to work in Cake way I tried with normal SQL query and it works fine.
What could be wrong here?
Have you tried setting the recursive property before the find? Eg:
$this->Chat->recursive = 3;
You may need to set this after $this->Chat->bindModel, but I am not sure if this will make a difference or not. You will also need to re-bind the User model before each find, if, for example, your find queries are run within a loop ...

cant get count of the data properly

I have a model template which hasmany themes.I want to show the list of templates with count of themes.I am using this
$this->Template->bindModel(
array(
'hasMany' => array(
'TemplateTheme' => array(
'className' => 'TemplateTheme',
'fields' => 'count(TemplateTheme.id) AS themes'
)
)
), false ...
it gives me 2 templates.But it gives me all the 3 themes count in the first template whereas 2 themes belongs to template 1 and the third theme belongs to template 2
in the query it is using id IN(template_id1,template_id2)
Any idea how to do this?
You are doing a common mistake, you are counting everyrow each time since you are not using group by, you should do is group by Template.id when you do your search. Butttttttt.... has many wont do a join :( so you have to force it a littleor use something like linkable component
example
$join = array(
array('table' => 'templateThemes',
'alias' => 'TemplateTheme',
'type' => 'LEFT',
'conditions' => array(
'Template.id = TemplateTheme.Template_id',
)
)
);
$fields = array('Template.id','count(TemplateTheme.id) AS themes');
$this->Template->find('all', array('fields'=>$fields, 'joins'=>$join', $group =>array('Template.id')));
You may also do it in reverse since belongsTo does the join something like this
in your model (it is always recommended to put it static in your model unless is not a normal association)
var belongsTo = array(
'Template'=> array(
'classname' => 'Template',
'foreign_key' => 'template_id'
);
and in controller
$fields = array('Template.id','count(TemplateTheme.id) AS themes');
$this->Template->find('all', array('fields'=>$fields, $group =>array('Template.id')));
Hope this helps you, if not just comment

Specifying record criteria on more than one model in one pagination call

I am stuck on pagination in CakePHP 1.3. I am trying to paginate feePayment records based on certain criteria.
feePayments belongs to Students which in turn belongs YearGroups.
I want to paginate 'unpaid' feePayments for each year group. The problem I am having is that the SQL query seems to only take into account the conditions I specified for the FeePayment model and ignores the YearGroup criteria so only overdue unpaid records are returned regardless of the year group specified.
Here is my code:
function unpaidClass($id) {
$this->paginate = array(
'FeePayment' => array ('recursive' => 1, 'conditions' => array('FeePayment.status' => 'Unpaid', 'FeePayment.due_date <= ' => date("Y-m-d"))),
'YearGroup' => array ('recursive' => 1, 'conditions' => array('YearGroup.school_year' => $id))
);
$this->set('feePayments', $this->paginate());
}
Hope this makes sense, appreciate any help.
Thanks,
Sid.
You should consider using the Containable behavior. This behavior allows you to group the necessary data you want without relaying on the "recursiveness" of your query. You can place conditions on your contained data similar to the way you would in your queries and is a more permanent way to structure data across your application instead of specifying conditions over and over again in each query.
Paginate should automatically pick up these associations when you Paginate your main model. Here's an example of what I mean here: http://cakephp.1045679.n5.nabble.com/Paginate-with-Containable-td1300971.html#a1300971
These should make your task easier.
After a lot of searching the net and reading CakePHP's documentation, here is the solution I came up with:
function unpaidClass($id) {
$this->FeePayment->unbindModel(array(
'belongsTo' => array('Student')
), $reset = 0);
$this->FeePayment->bindModel(array(
'belongsTo' => array(
'Student' => array(
'foreignKey' => false,
'conditions' => array('Student.id = FeePayment.student_id')
),
'YearGroup' => array(
'foreignKey' => false,
'conditions' => array('YearGroup.id = Student.year_group_id')
)
)
), $reset = 0);
$this->paginate = array(
'contain' => array('Student','YearGroup'),
'conditions' => array('YearGroup.school_year' => $id,
'FeePayment.status' => 'Unpaid',
'FeePayment.due_date <= ' => date("Y-m-d")));
$this->set('feePayments', $this->paginate());
}

Categories