Cakephp pagination with random order? - cakephp

Ok, I have looked and looked but cannot seem to find anything on this anywhere. I have a display of results that are paginated beautifully, but they currently display in ascending order. I'd like for them to display in random order. Here is my current controller code:
public function condos() {
$this->paginate['Unit']=array(
'limit'=>9,
'contain'=>array(
'User'=>array(
'id', 'user_name', 'area_code', 'exchange', 'sln', 'email'),
'Complex'=>array('id','complex_name', 'realname', 'photo1','photo2','photo3','photo4','photo5', 'complex_website')
),
'conditions'=>array(
'Unit.type'=>array('condo', 'rentalco'),
'Unit.active'=>1)
);
$data = $this->paginate('Unit');
$this->set('allcondos', $data);
}

For anyone else finding this - the actual answer is to generate a seed (a float between 0 and 1), and save it to the session before the RAND() sort is necessary (in the controller's beforeFilter()). Then:
$this->paginate['order'] = sprintf('RAND(%f), $this->Session->read('seed'));
This preserves the RAND() seed between calls to the paginator, and preserves the overall order of the results between requests.

This seems to work pretty well on CakePHP 2 for me:
$this->paginate = array(
'order' => 'RAND()'
);
This is using the MySQL RAND() function, which Cake just passes on to the database.
EDIT: Now that I think about it, this is not a good idea, because the order is not maintained between pages. I can't think of a good way off the top of my head to randomize the order and maintain continuity between pages. Maybe if you were to shuffle the items on the page with JavaScript?

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 query with big database and pagination

I'm using cakephp and Mysql to develop a shop System where a register all the products I sell, the system was working fine till I started to make some tests, I have introduced more than 30.000 registers of products and from there on, I can't search for my products cause I'm having this error:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32 bytes)
From my research if found that this is related to memory and I could increase the memory but also I have realized that these would be a momentum solution.
I have seen that the function called in the controller products is this:
public function search() {
$this->Product->recursive = 0;
$this->set('products', $this->paginate());
$products = $this->Product->find('all');
$this->set(compact('products'));
}
and from this I see that this could be the problem cause it's fetching all the products.
So the help I need is how to improve this, what would be the best approach for this. On my research one of the solution I was expecting to get is probably to have this query but fetching like 10 by time I don't know if it's possible.
Thanks in Advance.
PS
Remove the last two lines.
You're doing the paginated call, which is what you want, but then throwing it away and fetching ALL the records. Which is pretty expensive when you have lots of data.
public function search() {
$this->Product->recursive = 0;
$this->set('products', $this->paginate());
}
If you are using CakePHP version 2 than this is the right way to use paginator, you can also limit the data per page with something else like 10, 20, 30 etc. so that all data can't be load at once.
public function search() {
$this->Paginator->settings = array(
'limit' => 10,
'recursive' => 0
);
$products = $this->Paginator->paginate('Product');
$this->set(compact('products'));
}

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.

Cake HABTM Query, Order By Rand()

I'm aware that Cake HABTM associations are tricky at the best of times, but I seem to be making life even harder for myself...
If I want to return a random Item from the db, I can do it as follows in the Item model:
$random = $this->find('first', array(
'order' => 'rand()'
));
and if I want to find all the Items that are in a certain Category (where Item has a HABTM relationship to Categories), I know I can get a result set through $this->Categories->find.
My question is: how can I combine the two, so I can return a random Item that belongs to a specified Category? Is there any easy way? (If not, I'll gladly take any suggestions for a laborious way, as long as it works ;)
ETA: I can get some of the way with Containable, maybe: say I add the line
'contain' => array('Categories'=>array('conditions'=>array('Categories.id'=>1))),
then the Item results that I don't want come back with an empty Categories array, to distinguish them from "good" items. But really I don't want said Item results to be returned at all...
ETA(2): I can get a workaround going by unsetting my results in the afterFind if the Categories array is empty (thanks to http://nuts-and-bolts-of-cakephp.com/2008/08/06/filtering-results-returned-by-containable-behavior/ for the tip), and then having my random find function not give up until it gets a result:
while (!is_array($item)) {
$item = $this->random($cat);
}
but ugh, could this be any clunkier? Anyway, time for me to stop editing my question, and to go away and sleep on it instead!
Try this:
<?php
$this->Item->bindModel(array('hasOne' => array('ItemsCategory')));
$random = $this->Item->find('all', array(
'order' => 'rand()',
'conditions' => array('ItemsCategory.category_id' => '1')
));
?>

Select from a table where associated table fieldname = 'x' in Cake PHP

In CakePHP, I have two tables, Countries & Networks. They have a HABTM relationship and are joined by countries_networks.
I'm trying to get all countries from the countries table where the 'name' field in Networks = 'o2'
I've realised I can't do this using a basic find(), so I've been experimenting with the containable behaviour. I have managed to restrict the returned data, but it looks as though 'containable' doesn't exactly work as I want. Heres my code:
$countries = $this->Country->find('all', array('contain' => array(
'Network' => array(
'conditions' => array('Network.name =' => "o2"),
)
)));
This query however returns ALL countries, and the Network.name if its 'o2'. What I really need to do is return ONLY the countries that have a Network.name of 'o2', and no others.
Can anyone help? Thanks.
"=' =>"
What is it? there is no needed to use "=" symbol after "Network.name"
Your query returns you exactly what your ask.
Try to select from Network.
$countries = $this->Country->Network->find('first', array(
'conditions' => array('Network.name' => "o2"),
'contain' => array('Country')
));
$countries = $countries['Country'];
You should be able to do something like this:
$this->Country->hasAndBelongsToMany['Network']['conditions'] = array('Network.name'=>'o2');
$myCountries = $this->Country->find('all');
I haven't tested this, but it should get you pretty close to where you need to be.
Also, bear in mind that changing the hasAndBelongsToMany array like this will affect all subsequent queries against that model during the current page load (but it will be reset the next time the model is instantiated).
I'm on my way out the door, so sorry for the brief explanation.
I think what you want is a HABTM relationship, but to be able to filter based on the associated model's data. To do this, check out the "Containable" behavior in Cake's manual. Pretty sure that's what you're after.

Resources