CakePHP pagination not working for a customized query - cakephp

I have a query in CakePHP which somehow joins other tables to get the result I wanted.
$options = array();
$options['fields'] = array('Order.*', 'OrderCustomer.*', 'Payment.*','OrderCoupon.*', 'OrderSalesrep.*', 'OrderPackage.*');
$options['order'] = "Order.added DESC";
$options['joins'] = array(
array(
'table'=>'ordercustomers',
'alias'=>'OrderCustomer',
'type'=>'inner',
'conditions'=>array('OrderCustomer.order_id=Order.id')
),
array(
'table'=>'payments',
'alias'=>'Payment',
'type'=>'inner',
'conditions'=>array('Payment.id=Order.payment_id')
),
array(
'table'=>'ordercoupons',
'alias'=>'OrderCoupon',
'type'=>'left',
'conditions'=>array('OrderCoupon.order_id=Order.id')
),
array(
'table'=>'ordersalesreps',
'alias'=>'OrderSalesrep',
'type'=>'left',
'conditions'=>array('OrderSalesrep.order_id=Order.id')
),
array(
'table'=>'orderpackages',
'alias'=>'OrderPackage',
'type'=>'left',
'conditions'=>array('OrderPackage.order_id=Order.id')
)
);
$orders = $this->Order->find('all', $options);
$pageSettings = array();
$pageSettings['limit'] = 20;
$pageSettings['order'] = array('Order.added'=>'desc');
$this->Paginator->settings = $pageSettings;
$this->set('orders', $this->Paginator->paginate());
And then I want to paginate it on my view. Somehow it doesn't work. However I did try having only these line $orders = $this->Order->find('all', $options);$this->set('orders', $this->Paginator->paginate()); and it works. Is the pagination only working for direct queries to the model or will it also work to queries like my query above which joins other tables. Thanks.

(Don't know your cakephp version, assuming 2.x)
You are confused. You do not find something and then paginate it. The paginator component does a whole query on it's own. So what you are doing is basically
here, cakephp, find me this complex query
got it, thanks a lot, now paginate me this completely unrelated query
hey, wait, that didn't work
So, don't use simple finds for queries intended to be paginated, and likewise, don't use Paginator for simple find queries intended for something else (not saying you are doing it now, just a friendly reminder).
For the problem at hand cakephp docs has examples. This one for example
$this->Paginator->settings = array(
'conditions' => array('Recipe.title LIKE' => 'a%'),
'limit' => 10
);
$data = $this->Paginator->paginate('Recipe');
$this->set(compact('data'));
So, changing it to your problem would be something like
//the same options array
//$pageSettings = array();
$options['limit'] = 20; //this is by default, I think, so you can omit it
$options['order'] = array('Order.added'=>'desc');
$this->Paginator->settings = $options;
$this->set('orders', $this->Paginator->paginate());
and you are done. If there are errors, you can handle them case by case (or ask them here if you don't find a solution online).
Here is the Paginator API if that helps you clear doubts about functions and parameters required.

Related

Ways to use array in cakephp

Hello I am having a tought time figuring out how to use arrays in cakephp. right now i have a view with 2 columns, active and startYear. i need to grab the start years for all of the columns in the view and sho i have this code.
public function initialize(array $config)
{
$this->setTable('odb.SchoolYear');
}
controller
public function index()
{
$deleteTable = $this->loadModel('DeletedTranscripts');
$this->$deleteTable->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
$this->set('startYear',$deleteTable );
}
once i have the array captured and put into lets say startYear can in input a statement like this into my dropdown list to populate it?
<div class="dropdown-menu">
<a class="dropdown-item" href="#"><?= $delete->startYear; ?></a>
</div>
i have been looking for answers for quite awhile any help would be awesome.
Couple of things:
Loading Tables in CakePHP
For this line:
$deleteTable = $this->loadModel('DeletedTranscripts');
While you can get a table this way, there's really no reason to set the return of loadModel to a variable. This function sets a property of the same name on the Controller, which almost correctly used on the next line. Just use:
$this->loadModel('DeletedTranscripts');
Then you can start referencing this Table with:
$this->DeletedTranscripts
Additionally, if you're in say the DeletedTranscriptsController - the corresponding Table is loaded for you automatically, this call might be unnecessary entirely.
Getting Query Results
Next, you're close on the query part, you've can start to build a new Query with:
$this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
But note that the find() function does not immediately return results - it's just building a query. You can continue to modify this query with additional functions (like ->where() or ->contain()).
To get results from a query you need to call something like toArray() to get all results or first() to get a single one, like so:
$deletedTranscriptsList = $this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
))->toArray();
Sending data to the view
Now that you've got the list, set that so it's available in your view as an array:
$this->set('startYear', $deletedTranscriptsList );
See also:
Using Finders to Load Data
Setting View Variables
I also noticed you've had a few other related questions recently - CakePHP's docs are really good overall, it does cover these systems pretty well. I'd encourage you to read up as much as possible on Controller's & View's.
I'd also maybe suggest running through the CMS Tutorial if you've not done so already, the section covering Controllers might help explain a number of CakePHP concepts related here & has some great working examples.
Hope this helps!

CakePHP Setting recursive for select joins

I create a search on models like this:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'recursive' => 5
);
$survey = $this->CompletedSurvey->find('first', $options);
The way I have my models set up, this will return five models (do to their various joins) each recursed up to 5 times (if available). The problem is that I only want one of these models to be recursed X5. The others don't need to be.
Is there a way to tell the find function which tables to recurse and at what level to recurse them to? So, telling cake which models to recurse and at what level for each one?
Please read the CakePHP cookbook regarding the recursive property on models, as it does not work the way you are thinking. http://book.cakephp.org/2.0/en/models/model-attributes.html#recursive
What you're looking for is the containable behavior, where you can specify exactly which models to return. Please see the cookbook on how to use containable. http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Well, this is not exactly recursive, but the only way to access deep relations and avoid to use recursive on a main model is to use containable behavior:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'contain' => array(
'SomeModel.SomeOtherModel.AnotherModel.AnotherModel'
)
);
$survey = $this->CompletedSurvey->find('first', $options);
And don't forget to set containable behavior for CompletedSurvey model!
class CompletedSurvey extends AppModel {
public $actsAs = array('Containable');
}

Multi level ordering in cakePHP

Why don't cakePHP include all my associations into one SQL query? The only way I have been able to do this is using "joins", but I hoped belongsTo and Containable behaviour was enough.
Here is an example:
Post->belongsTo->Category->belongsTo->category_type
(All models are setup correctly and work.
listing posts with pagination in index, I try this:
public function index() {
$this->paginate = array( 'contain' => array('Category' => array('CategoryType')));
$this->set('posts', $this->paginate());
}
This fetches the array correctly, but it does it in many SQLs like this:
SELECT `Post`.`id`, `Post`.`name`, `Post`.`content`, `Post`.`category_id`, `Category`.`id`, `Category`.`name`, `Category`.`category_type_id` FROM `unit_app`.`post` AS `Post` LEFT JOIN `unit_app`.`categories` AS `Category` ON (`Post`.`category_id` = `Category`.`id`) WHERE 1 = 1 LIMIT 20
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 1
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
This makes it difficult to order this query on CategoryType.name ASC.
Any suggestions?
If joins are only option, do I have to unbind the models before querying?
Will pagination work fine with joins?
Note! this is just a small part of all models, the resulting post->index need to fetch many other models through similar associations also.
(tested on cake 2.2.0 and v2.4.0-dev, php v5.4.11)
UPDATE! ---------
I just wanted to show my findings. I have now solved this without joins, but I had to re-bind in the model to get it working.
This is basically what I did to get it to work (also with paginations and sorts):
In Post model:
Added a bind function:
$this->unbindModel(array(
'belongsTo' => array('Category')
));
$this->bindModel(array(
'hasOne' => array(
'Category' => array(
'foreignKey' => false,
'conditions' => array('Category.id = Post.category_id')
),
'CategoryType' => array(
'foreignKey' => false,
'conditions' => array('CategoryType.id = Category.category_type_id')
))));
Then I added this to my index in Post controller:
$this->Post->bindCategory();
$this->paginate = array('contain' => array('Category' ,'CategoryType'));
$this->set('posts', $this->paginate());
I include table headers also just for documentation:
<th><?php echo $this->Paginator->sort('CategoryType.name', 'Type'); ?></th>
<th><?php echo $this->Paginator->sort('Category.name', 'Category'); ?></th>
I hope this post can help others as well :)
I am also going to test this Behaviour to see if I can omit all the bind-functions as well: https://github.com/siran/linkable/
There are lots of plugins to cake, but cake should have a "certification" of the plugins. It is quite difficult to find the fully working and tested ones on github :)
I also miss a site like railscasts.com just for cake :D
/MartOn
Yes, you must use JOINs to be able to order based on an associated models results.
Yes, you can paginate with JOINs. Just pass your options (including JOINs) to your paginate prior to actually calling $this->paginate();. (there are many resources online for how to paginate with JOINs)

CakePHP -- conditions ignored when using paginate() twice in a single action

I have a photoblog built on CakePHP 2.0 with a data structure that looks like:
POSTS <-habtm-> TAGS <-habtm-> IMAGES
I am building an AJAX-based feature to find all blog posts and images that match a given tag. Page 1 of Posts and page 1 of Images are loaded into adjacent panels when a tag is first selected. After that, they can be paged through independently. For the most part this is working fine, except when I am fetching the initial pages of data.
I am using paginate() twice in the first action -- once to get my Posts and a second time to get the Images. The problem is that the conditions I assign to paginate() for the second model in the sequence are completely ignored. Individually they both work fine, and switching their order has confirmed it's a sequence-dependent problem for me, rather than restricted to one of the models or the other.
I've searched to see if anyone else has encountered similar problems in the past, but this is either an unusual design choice on my part or I'm not finding the right search query.
My basic $paginate array is declared as follows in my TagsController.php:
public $paginate = array(
"PostsTag" => array(
"limit" => 4,
"order" => "Post.id DESC",
"contain" => array(
"Tag",
"Post" => array("fields" => array(
"id", "title", "created"))
),
"group" => array("Post.id")
),
"ImagesTag" => array(
"limit" => 4,
"order" => "Image.id DESC",
"contain" => array(
"Tag",
"Image" => array("fields" => array(
"id", "title", "url", "created", "gallery"))
),
"group" => array("Image.id")
)
);
From my main search action I call two private functions:
$posts = $this->post_pagination($tagIds);
$images = $this->image_pagination($tagIds);
which add the limiting conditions to $paginate and look like this:
private function post_pagination($tags, $page = 1) {
$this->paginate['PostsTag']['conditions'] = array(
"status" => 1,
"OR" => array("tag_id" => $tags)
);
$this->paginate['PostsTag']['page'] = $page;
return $this->paginate("PostsTag");
}
private function image_pagination($tags, $page = 1) {
$this->paginate['ImagesTag']['conditions'] = array(
"gallery" => 1,
"OR" => array("tag_id" => $tags)
);
$this->paginate['ImagesTag']['page'] = $page;
return $this->paginate("ImagesTag");
}
Cake is respecting limit, order, contain, etc. without issue, but drops the ball on conditions specifically for whichever model I try to paginate over second. It feeds me back the first 4 results ordered properly, but completely unfiltered. I do not think my somewhat complicated conditions are at fault either -- as long as I don't break syntax, I can type completely random strings into conditions for the second paginate() and get back identical results.
Any help or suggestions are greatly appreciated.
[edit] Here is an SQL dump of the second paginate() query:
SELECT `PostsTag`.`id`, `PostsTag`.`post_id`, `PostsTag`.`tag_id`,
`Tag`.`id`, `Tag`.`name`, `Post`.`id`, `Post`.`title`, `Post`.`created`
FROM `posts_tags` AS `PostsTag`
LEFT JOIN `tags` AS `Tag` ON (`PostsTag`.`tag_id` = `Tag`.`id`)
LEFT JOIN `posts` AS `Post` ON (`PostsTag`.`post_id` = `Post`.`id`)
WHERE 1 = 1
GROUP BY `Post`.`id`
ORDER BY `Post`.`id`
DESC LIMIT 4
As you can see, Cake is generating a WHERE 1 = 1 in place of my conditions.
DEAR PEOPLE FROM THE FUTURE: Here's what we've figured out so far...
OP is correct that YourController::$paginate is only fed into the PaginatorComponent once. If you need to call YourController::paginate() again with different options, you'll need to unload the component first, e.g.:
$this->Components->unload('Paginator');
Then, the next time you call YourController::paginate(), it will reload whatever's in the YourController::$paginate property.
So upon some more poking around I discovered the following:
Any alterations made to $paginate after an initial paginate() call is made are not carried through to the Paginator component. This applies to conditions, order, limit, etc.
So doing this:
$this->paginate['<model1>']['conditions'] = array( ... );
$model1Results = $this->paginate("<model1>");
$this->paginate['<model2>']['conditions'] = array( ... );
$model2Results = $this->paginate("<model2>");
Will return results for <model1> that obey the new conditions/order/limit/whatever you've applied, but your results for <model2> will be based on the original conditions defined for it in $paginate. Your controller will see the updates to $paginate just fine, but it appears $paginate can only be grabbed by Paginator once.
The workaround I have found is to make any and all changes to $paginate BEFORE the first paginate() call, so:
$this->paginate['<model1>']['conditions'] = array( ... );
$this->paginate['<model2>']['conditions'] = array( ... );
$model1Results = $this->paginate('<model1>');
$model2Results = $this->paginate('<model2>');
I've been poking around in PaginatorComponent.php to figure out why things work this way, and any further insight would, of course, be appreciated.

Cakephp complex hasMany relationship query

I'm very new to CakePHP. I want to do a query in my database like this
SELECT m.id, l.*, lp.picture_path
FROM member m INNER JOIN listing l ON m.member_id = l.member_id
INNER JOIN listingPicture lp ON l.listing_id = lp.listing_id
WHERE lp.picture_default='1'
I have 3 models in my cakephp : Member, Listing, and ListingPicture each with the following relationship
Member hasMany Listing
Listing hasMany ListingPicture
Listing belongsTo Member
ListingPicture belongsTo Listing
From my Member controller how do I execute the query above ?
I've tried
$this->Member->Listing->find("all")
... which works well but when I added a conditions like this:
$this->Member->Listing->find('all', array(
'conditions' => array('ListingPicture.picture_default'=>'1')));
... I get an error.
Because I'm new to CakePHP, I don't know how to see the error.
Can anyone advise me how I can perform this query?
Make sure to set your model as:
public $actsAs = array('Containable');
Then use CakePHP's containable behavior to include only the associated data you want, with specified fields and conditions.
$this->Member->Listing->find('all', array(
'fields' => array('*'),
'contain' => array(
'Member' => array(
'fields' => array('id')
)
'ListingPicture' => array(
'conditions' => array('ListingPicture.picture_default' => '1')
'fields' => array('picture_path')
)
)
));
To follow with the MVC concept, it's suggested to keep your finds in a Model as opposed to a controller. It's not required, but - it makes it much easier to know exactly where all finds are, and keeps with the "Fat model / Skinny controler" mantra. In this case, it'd be something like:
//in the Member Controller
$listings = $this->Member->Listing->getListings();
//in the Listing Model
function getListings() {
$listings = $this->find('all', ...
return $listings;
}
You should give the condition at the time of binding ListPicture to Listing.
$this->Member->Listing->bindModel(array(
'ListPicture'=>array(
'condtions'=>array('ListingPicture.picture_default'=>'1')
))
);

Resources