CakePHP Paginate Sort on Contain Data - php

It's a simple application for tracking user hobbies. A user can have many hobbies, which are organized into different hobby groups.
So I have 4 related tables: users, users_hobbies, hobbies, hobby_groups.
users is a has-and-belongs-to-many relation to hobbies, via the users_hobbies join table.
hobbies belongs-to a hobby_group, hobby_group has-many hobbies
Makes sense, pretty simple so far.
In UsersHobbiesController, I have:
$paginate = [
'contain' => [
'Hobby' => [
'fields' => ['id', 'name'],
'HobbyGroup' => [
'fields' => ['id', 'name']
]
]
]
];
Also, in my index() function, I build the pagination.
$usersHobbies = $this->paginate('UsersHobby');
$this->set(compact('usersHobbies));
When I access the $usersHobbies variable in my index.ctp View file, it has the data I want for building an output table. It looks like:
array(
(int) 0 => array(
'UsersHobby' => array(...),
'Hobby' => array(
'id' => 'abc-def-ghi',
'name' => 'Jet Skiing',
'HobbyGroup' => array(
'id' => 'oiuy-trew-qldf',
'name' => 'Watersports'
)
)
)
....
)
To sort the output table, I've added some sorting columns:
<th><?php echo $this->Paginator->sort('Hobby.name', 'Hobby'); ?></th>
<th><?php echo $this->Paginator->sort('Hobby.HobbyGroup.id', 'Hobby'); ?></th>
The first header works for sorting on the Hobby name. But I can't get the second header to sort for the HobbyGroup. Is there a simple way to do this?
I've searched for a few hours on StackOverflow, but can't seem to find an answer that works for me. Thanks.

This will not work as you expect because of how CakePHP fetches data.
Depending how you setup your relationships what will happen is that Cake will first select the Hobbies and then do another query for Hobby Groups.
So any sorting you apply to the second query will be generally useless and lost when that data is mapped to the list of Hobbies.
So you have
SELECT hobbies....
Then another select will be issued
SELECT hooby_groups WHERE hobby_id IN [list of ids ] ORDER BY hobby_groups_id
The data from the second select will be mapped to the first one, so your order by did not do much!
Depending on what CakePHP version you are using you can do a couple of things:
Define the relationship to use INNER JOIN strategy so will avoid creating two SELECT statements. This way the order by cause will sort all the data as you expect. The caveat is that with INNER JOIN Hobbies that do not have any group associated will never be selected by your pagination.
If you are using CakePHP 2.x you could have a look at Custom Query Pagination and build the query as you need it to be for it to work.
If you're using CakePHP 3.x you have a query builder there and the Paginator can paginate any query, so you can do you inner joins there.
To help you out you can use the DebugKit from CakePHP to see what actual queries are being run and how tweaking this and that parameter affects how the query is being generated. You will see where the order by is applied and why it may not work.
In CakePHP 2.x you can use type to control how the joining is done. You need to use INNER

Related

How to get all the details from multiple tables in Cakephp?

I have two different tables as Post and Post_Info.
I want to get all the details using post_id in cake php from both the tables. The Post_Info table having post_id as foreign key and other related details.
I am new to Cakephp and I don't know how to get data from multiple tables in Cakephp.
I can get data from Post table using following query in Cakephp.
Following code is getting data from Post table.
$userdata = $this->Post->find('first', array(
'conditions' => array('Post.post_id' => $post_id)
));
To achieve this you have to do two things.
Make a relation of belongsTo in PostInfoTable.php in your model folder. you can read more about it here.
Once you have made the relationship all you have to do is use "contain" in your query along with the Model name. Your query will be modified as below
$userdata = $this->Post->find('first', array(
'conditions' => array('Post.post_id' => $post_id),
'contain' => ['PostInfo']
));
And you are good to go.
Moreover, I will recommend you to go through the documentation at least once it will be a great help to you.

Optimize query in Laravel Backpack n-n relationships

I am building a backend panel for a website with Laravel Backpack. It is really nice, but I have noticed that relationship queries are very expensive.
I have two models: Product and Center with a many to many relationship between them. In my CenterCrudController I have defined a field this way:
$this->crud->addColumns([
// More fields...
[
'label' => 'Products',
'type' => 'select2_multiple',
'name' => 'products', // the method that defines the relationship in your Model
'entity' => 'products', // the method that defines the relationship in your Model
'attribute' => 'name', // foreign key attribute that is shown to user
'model' => 'App\Models\Product', // foreign key model
'pivot' => true, // on create&update, do you need to add/delete pivot table entries?
],
// More fields...
]);
It works fine, showing a select multiple field with related models. But the query used is SELECT * FROM products, which is highly expensive (table products have thousands of records with about 25 columns).
In this example I only need id and name fields. I am looking for something like Query Builder select() method.
Is there a way for optimizing this type of query?
Thanks in advance!
Not sure if this is actually an answer, but I'll post it anyway.
The best solution (as pointed by #tabacitu) was using select2_from_ajax field. It doesn't slow page load and make an ajax request for retrieving data only when user clicks on the select field.

How to use a relationship in the where parameter when finding records using the pods framework?

my question has to do with the pods framework plugin for wordpress sites. I am using pods version 2.2 and have been having trouble with the where parameter in the find() function.
I'd be suprised if I am the first person to encounter this problem but I have searched extensively and haven't found anyone providing an answer (or question at that).
Anyway, I'll give an example to highlight my problem.
Say I have a Bands Pod and a Records Pod and these two pods have a bi-directional multi-select relationship between them (that is, an n to n relationship). Hence a band can have numerous records and a record can have multiple bands. Further, the relationship exists between the fields BandsPod('records') and RecordsPod('bands').
Now, I can retrieve all records in the records pod like so (note the where is commented out):
$pods = pods('records');
$params = array(
'select' => 't.*',
'limit' => -1
//,'where' => '???'
);
$pods->find($params);
Then do whatever I want with them, e.g. template(), fetch(), etc.
My trouble occurs when I want to filter by band. What should my where statement be if I want to retrieve all records by band with id 1?
My view is that it should be something like this:
$params = array(
'select' => 't.*',
'limit' => -1,
'where' => '1 IN bands.id'
);
$pods->find($params);
However, this does not work (not that I especially expected it to).
It would also be desirable to know how this would work for filtering by multiple bands. E.g.
'where' => '(1, 2) IN bands.id'
As I said before, I've been trying to get this to work for some time and with little joy. I have managed to get it working but in a very ugly way. I.e.
Get bands from Bands Pod with relevant band ids,
Collect all ids of records from the bands pod records field,
Write params for Records Pod
Use the ids in the where statement to check they match with t.id,
$pods->find(); //where $pods=pods('records');
Thanks in advance for taking the time to read this and any answers you may give.
Cheers,
Joe
N.B. I know about the filters() function and it does not do what I'm after. I'd like stuff specifically for the where statement please.
You would want:
'where' => 'bands.id IN ( 1, 2 )'
If your bands are a custom post type, it would be:
'where' => 'bands.ID IN ( 1, 2 )'

ZF2: Populating select element in form with date from database

I have a database with a simple one-to-many relationship that looks like this:
Tables Company Category
Rows ID ID
Name Name
Category_ID
I have a forms where I can add, edit and delete the company's or category's name, which works fine. Entering the category by ID also works, but is obviously terrible to use. What I want is a simple select element in the form to pick from an existing category.
The code I have to generate the <select> is:
$this->add(array(
'name' => 'Categorie',
'type' => 'Zend\Form\Element\Select',
'attributes' => array(
'options' => $categories,
),
'options' => array(
'label' => 'Categorie',
),
));
From what I could find in the (extremely sparse) ZF2 documentation, I should be using a Hydrator to fetch data into the $categories variable, but I'm not sure where to go from there.
Any sort of example or tips would be well appreciated!
Here's a very good article about forms.
My solution is based on this tutorial and it is (almost) working well. I don't know if you use Doctrine for your project or not, but I think it would be a very good idea!
I also heavily use select elements. In the given fieldset, I generate value options for a select with this piece of code:
$opt= Registry::get('entityManager')->getRepository('My\Entity\Categories')
->getCategoriesForCombobox();
$this->get('category')->setValueOptions($opt);
If you (plan to) use Doctrine, you should read about repositories very carefully in the Doctrine documentation. If you use collections in the form, the topic about associations is also a must read.

Building conditional statement across multiple models (CakePHP 1.2.5)

Let's say I have 3 models: User, Region, Country.
User belongsTo Region
Region belongsTo Country
Each of these models is using the Containable behavior. I'm attempting to find users from the country with code 'US'. Here's what I'm attempting:
$users = $this->User->find('all', array(
'conditions' => array('Country.code' => 'US'),
'contain' => array('Region.Country'),
));
CakePHP is separating this into 2 queries:
First, it is SELECTing the ID's for all countries with code 'US'.
Then it is using those ID's to SELECT all users JOINing regions where region.country_id is in that list of country ID's previously retrieved.
As a result, I end up with SQL errors in my app since my conditions array contains a reference to Country.code, and the second query that Cake builds doesn't JOIN countries.
The best Cake solution I see is to build a sub-query as described in the Complex Find Conditions portion of the manual. However, this seems very convoluted, and is more of a hack than I would like to implement. Is there an easier way that I'm overlooking?
Nate Abele (former lead dev of CakePHP) wrote an article about doing ad hoc joins which might help.
Put the conditions in the contain as well. E.g:
$users = $this->User->find('all', array('contain' => array(
'Region' => array(
'Country' => array(
'conditions' => array('Country.code' => 'US'),
),
),
)));
See the second-to-last example in the Containable manual

Categories