Cakephp Paginate Find - cakephp

I want to list the posts of a given user. It work but paginate is not accurate.
My code is the following
public function index($userid = null) {
if ($this->Post->exists($userid)) {
$this->set('posts',$this->Post->find('all',array('conditions'=>array('user_id'=>$userid))),
$this->paginate());
} else
{
$this->Post->recursive = 0;
$this->set('posts', $this->paginate());
}
The result give the correct list --> 3 posts, but the paginator display page number 1 and 2
Can you help me?
Thank you

Refer to the documentation
The code in the question is quite confused.
find
The find method only has two parameters:
find(string $type = 'first', array $params = array())
The third parameter (the result of calling paginate) isn't used and will be ignored - but it will setup the view variables for the pagination helper, based on the conditions used in the paginate call - there are no conditions being used.
It is not possible to paginate the result of a find call - to do so restructure the code to call paginate instead of find.
paginate
The paginate method is just a proxy for the paginator component - it can be used in several ways, this one (controller code example):
$this->paginate($conditions)
Is the most appropriate usage for the case in the question i.e. the complete action code should be similar to:
public function index($userId = null) {
$conditions = array();
if ($userId) {
$conditions['Post.user_id'] = $userId;
}
$this->set('posts',$this->paginate($conditions));
}
Note that logically, if a user id is requested that doesn't exist the response should be nothing - not everything.

I'm quite sure that conditions for paginate do now work that way.
If you want to set conditions for paginations you should do it as follows:
$this->paginate = array('conditions' => array('Post.user_id' => $userid)));
$this->set('posts', $this->paginate());
And yes, the result stored in $posts ( in view ) will be proper as you assigned proper find result to it, meanwhile you've paginated post model without any conditions whatsoever.

First off, you're checking to see if the post exists but using the $userid. Are you trying to see "if the user exists, get the posts for that user, or else get posts for ALL users"? As you have it right now, say you have the $userid = 159, but the max Post.id in your database is 28, then the condition is not being met because it is checking to see whether or not there is a Post with the id = 159 that exists, which it doesn't.
Second, your conditions are wrong. You are performing a find and then a paginate which are two separate queries. The conditions are being implemented on the find query but not the paginate but you are only displaying the find results.
public function index($userid = null) {
// setting recursive outside of if statement makes it applicable either way
$this->Post->recursive = 0;
// check if user exists
if ($this->Post->User->exists($userid)) {
// get posts for user
$this->set('posts', $this->paginate('Post', array('Post.user_id' => $userid));
}
else{
// get all posts
$this->set('posts', $this->paginate('Post'));
}
} // end index function

Related

How to not get some data based on records in another table with laravel eloquent?

Here's my Database Structure:
Products : ['id','name','image']
Request : ['id','marketer_id','distributor_id']
RequestItems : ['id','request_id','product_id','quantity']
Now this is just a short example of structure. what I'm trying to do is that, I have a page with request, which in this page I'm getting items in a request base on RequestItems table, and i want to add a button in my page to add product to this request, but i want to show products that are not exist in RequestItems.
I can make a condition to check before adding product to make sure user won't add 1 product 2 times in a request, but i also want to make it clear that user see products in add button which it's already exist in his request items.
I just need help for Query, I'm developing with Laravel & vue.js for SPA.
My Solution (But looking for better Solution) :
public function getRequestRepresentativeSideProducts(RepresentativesRequests $id, Request $request){
$products = DistributorProducts::where(
'distributor_id', $request->distributor_id
)
->latest()
->get();
$data = $id->items()->latest()->get();
$myArray = array();
for ($i = 0; $i < $data->count(); $i++)
{
$myArray[] = $data[$i]->product_id;
}
return $products->except($myArray);
}
Edit 01 : I've managed to get response with below query, but It's taking all data
$data = DistributorProducts::doesntHave('requestRepresentativeSideItems', 'and', function ($query){
$query->where('representative_request_id', '=', 1);
})->where('distributor_id', $request->distributor_id)
->latest()
->get();
$data = DistributorProducts::whereDoesntHave('requestRepresentativeSideItems', function (Builder $query) use ($id) {
$query->where('representative_request_id', 'like', $id->id);
})->where('distributor_id', $request->distributor_id)
->latest()
->get();
$data = DistributorProducts::whereDoesntHave('requestRepresentativeSideItems', function (Builder $query) use ($id) {
$query->where('representative_request_id', $id->id);
})->where('distributor_id', $request->distributor_id)
->latest()
->get();
I have 3 products for distributor_id which 2 of them are in request items, so my query should show 1 but will above query still I'm getting all products.
Edit 02: I've checked where from $query and used like too and changed request_id but didn't changed result at all. looks like it's just ignoring that part.
Presuming you have the id for the Request you wanted to filter out, the SQL query will be roughly looked like
SELECT * FROM `products` WHERE NOT EXISTS
(SELECT * FROM `request_items` where `products`.`id` = `product_id` and `request_id` = ? )
I had to make additional assumtion as you didnt provide the model, i'd just assume the following
class Product extends Model
{
public function requestItems()
{
return $this->hasMany(RequestItems::class);
}
}
class RequestItems extends Model
{
public function products()
{
return $this->belongsTo(Product::class);
}
}
Then we can use doesntHave query criteria to find products that dont have relationship with
the RequestItems for given request id (in this example its 1).
Products::doesntHave('requestItems', 'and',
function($query){ $query->where('request_id', '=', 1); }
);
Hope it helps. I havent tried it myself, but i believe it would works. You can use toSql
method to see its resulting SQL query.
Note: some insight on the differece of IN and EXISTS

Paginator Not Found when page number specified

I manually set the CakePHP Pagination values in my Usergals Controller like so, so as to Paginate a related Model (TreasuresUsergal) on the view of Usergal. Here is a simplified snippet from the UsergalController:
public function view($id = null) {
$this->loadModel('TreasuresUsergal');
$options['joins'] = array(
array('table' => 'treasures',
'alias' => 'Treasure2',
'type' => 'LEFT',
'conditions' => array(
'Treasure2.id = TreasuresUsergal.treasure_id',
)
)
);
$options['conditions']=array('TreasuresUsergal.usergal_id'=>$id);
$options['fields']=array('Treasure2.*','TreasuresUsergal.ord','TreasuresUsergal.comments');
$options['order']=array('TreasuresUsergal.ord'=>'asc');
$options['limit']=$limit;
$this->Paginator->settings = $options;
$treasures=$this->Paginator->paginate('TreasuresUsergal');
$this->set('treasures',$treasures);
}
So in the above example, $id is the value passed to the view function from the URL. There is a live example of this here:
http://collections.centerofthewest.org/usergals/view/20
As you can see, it works just fine for a single page. However, today I tested the Paginator in the view and discovered the "next" button does not work. The Counter, sorting, and Page numbers all load correctly - but anytime the actual named parameter "page:n" is passed (when n is greater than 1) I get a Not Found page with the following error:
Not Found
Error: The requested address '/usergals/view/20/page:2?url=%2Fusergals%2Fview%2F20' was not found on this server.
I must be missing something simple - I have experimented with the routes a little, but haven't been able to figure it out. Or perhaps I am missing some Paginator options? Or does it think its OutOfBounds when its not?
UPDATE / WORKAROUND
After some messing around, I have devised this workaround. Not as nice as I'd like, but here is the basic idea (error handling, etc can be added)
First, I added a check in beforeFilter to see if page paramter was set. If so, I change it to 'p' parameter and redirect.
I did this here because otherwise I had problems with the Not Found exception (see notes at bottom). So, in beforeFilter:
if (isset($this->params['named']['page'])){
$newurl=$this->params['named'];
$pg=$newurl['page'];
unset($newurl['page']);
$newurl['p']=$pg;
$this->redirect(array('action' => 'view/'.$this->params['pass'][0])+$newurl);
}
Then, in the 'view' function of the same controller, I added this along with the other Paginator options:
if (isset($this->params['named']['p'])) $options['page']=$this->params['named']['p'];
With this, the standard Paginator behavior seems to work fine in the view. Prev, next, etc.
If anyone has a better suggestion, I would love to hear it. I don't like the idea of having to redirect, but it works for now.
It's worth noting that adding this code (even just to experiment) - caused all of my pagination counts to stop working. The query in debug was correct, but the displayed counts were wrong:
try {
$this->Paginator->paginate();
} catch (NotFoundException $e) {
}

Cakephp calculate rank of posts by number of comments

I have a basic post and comments cakephp application.
I want to show a table with a column that shows rank of posts by number of comments.
I'd settle for a percentage of total comments by post.
Where in cakephp would I do this? Where would I do count of comments by post divided by total count of all comments?
Where: You do it in the model. That's good practice.
How: I see two valid methods (I mean, there's tons of alternatives, but these are the easiest and don't repeat code everywhere)
Use Virtual Fields. In your model do something like
public $virtualFields = array(
'rank' => '/*query*/' //sql query that get's the number you want
);
That will add a field (virtual, is not in the DB) everytime you do a find() of that model
Create your own function in the model an call it whenever you want
public getRank($postID) {
$post = $this->find('first', array('conditions'=>array('id'=>$postID);
$rank = /*do the calculation you want*/
return $rank;
}
You call that function from the controllers like find() or save() or any other
$this->Post->getRank($id);
If by any reason you want to have this rank everytime you call a Post, then in the afterFind() method of that post, add a call to that function:
public afterFind(array $results, boolean $primary = false) {
parent::afterFind($results, $primary);
foreach ($results as $i=>$result) {
$results[$i]['rank'] = $this->getRank($result['id']);
}
return $results;
}
I didn't test the afterFind function. So change it to fit your needs.

CakePHP User Lookup based on their ID

I thought this would be a relatively common thing to do, but I can't find examples anywhere, and the Cookbook's section on find() was not clear in the slightest on the subject. Maybe it's just something that's so simple Cake assumes you can just do it on your own.
All I'm looking to do here is retrieve a User's name (not the currently logged-in user…a different one) in Cake based on their ID passed to my by an array in the view.
Here's what I've got in the controller:
public function user_lookup($userID){
$this->User->flatten = false;
$this->User->recursive = 1;
$user = $this->User->find('first', array('conditions' => $userID));
//what now?
}
At this point, I don't even know if I'm on the right track…I assume this will return an array with the User's data, but how do I handle those results? How do I know what the array's gonna look like? Do I just return($cakeArray['first'].' '.$cakeArray['last'])? I dunno…
Help?
You need to use set to take the returned data, and make it accessible as a variable in your views. set is the main way you send data from your controller to your view.
public function user_lookup($userID){
$this->User->flatten = false;
$this->User->recursive = 1;
// added - minor improvement
if(!$this->User->exists($userID)) {
$this->redirect(array('action'=>'some_place'));
// the requested user doesn't exist; redirect or throw a 404 etc.
}
// we use $this->set() to store the data returned.
// It will be accessible in your view in a variable called `user`
// (or what ever you pass as the first parameter)
$this->set('user', $this->User->find('first', array('conditions' => $userID)));
}
// user_lookup.ctp - output the `user`
<?php echo $user['User']['username']; // eg ?>
<?php debug($user); // see what's acutally been returned ?>
more in the manual (this is fundamental cake stuff so might be worth having a good read)

Paginate from within a model in CakePHP

I have a function in my Event model called getEvents - you can pass limit, start and end dates, fields, event types, and event subtypes.
I read that paginate can accept all the parameters I'm using like joins, conditions, limit...etc just like a normal find can.
It returns data just fine when I don't try to paginate. But - I'd like to be able to pass it a paginate variable to tell it instead of doing this:
$this->recursive = -1;
$data = $this->find('all', $qOptions);
to do this:
$this->recursive = -1;
$data = $this->paginate($qOptions);
When I try that, though, it gives me lots of errors. I can specify the errors later if needed - for now, I guess I'm looking for - is this something that can be done? If so, how?
Is there another better way to do something like this? I spent enough time making this function do just what I want, and allowing all the options passed...etc - it just seems like a waste if I can't also use it for pagination. But - if it's not ideal, I'm ok hearing that too. Thanks in advance.
EDIT:
I'm reading other things online that say you shouldn't use paginate in your model, because it draws from URL variables, which defeats the MVC structure purpose. This makes sense, but does that mean I have to write the same joins/queries in both model and controller? And in every action that it's needed?
The way I figured out how I can keep my complex find in my model without having to rewrite it a second time in the controller is by passing a $paginate boolean variable.
If $paginate is true, it returns just the options created, which can then be used in the controller's pagination. If it's false (meaning we don't want to paginate), it returns the actual event results. So far this seems to be working.
In my getEvents() function (this method is in the Events model)
if($paginate) {
return $qOpts; // Just return the options for the paginate in the controller to use
} else {
$data = $this->find('all', $qOpts); // Return the actual events
return $data;
}
Then, in my Events/Index (events controller, index action - where I know I want pagination):
$this->Event->recursive = -1; // (or set recursive = -1 in the appModel)
$opts['paginate'] = true;
$paginateOptions = $this->Event->getEvents($opts);
$this->paginate = $paginateOptions; // Set paginate options to just-returned options
$data = $this->paginate('Event'); // Get paginate results
$this->set('data', $data); // Set variable to hold paginated results in view
The paginate() model method does not accept the same parameters as a find(). Specifically, find() wants an array of options, but paginate() wants every option passed individually. See Custom Query Pagination in the CakePHP book.
So, instead of:
$data = $this->paginate($qOptions);
You want something like:
$data = $this->paginate($qOptions['conditions'], $qOptions['fields'], ...);
EDIT
Custom model pagination isn't a function that you call. It's a function that you need to implement and will be called by the CakePHP framework. In the example in your question you are trying to manually call $this->paginate(...) from somewhere in your model. That doesn't work. Instead, do this.
In your model, implement the paginate and paginateCount methods.
function paginate($conditions, $fields, ...)
{
// return some data here based on the parameters passed
}
function paginateCount($conditions, ...)
{
// return some rowcount here based off the passed parameters
}
Then, in your controller you can use the standard pagination functions.
function index()
{
$this->paginate = array('MyModel' => array(
'conditions' => array(...),
'fields' => array(...),
));
$this->set('myobjects', $this->paginate('MyModel'));
}
Now, the Controller::paginate() function will grab the conditions and other data from the Controller::paginate parameter and, instead of passing it to your Model::find it will pass it to your custom Model::paginate() and Model::paginateCount() functions. So, the data that is returned is based on whatever you do in those two methods and not based on a standard find().
}
you can use this one which is working fine for me.
$condition="your where condition";
$this->paginate = array(
'fields' => array('AsinsBookhistory.id', 'AsinsBookhistory.reffer_id', 'AsinsBookhistory.ISBN','AsinsBookhistory.image','AsinsBookhistory.title','AsinsBookhistory.last_updatedtime'),
'conditions' => $condition,
'group' => array('AsinsBookhistory.ISBN'),
'order' => array('AsinsBookhistory.last_updatedtime' => 'desc')
);
$this->set('lastvisitedbooks', $this->paginate('AsinsBookhistory'));
$paginate array are similar to the parameters of the Model->find('all') method, that is: conditions, fields, order, limit, page, contain, joins, and recursive.
So you can define your conditions like this :
var $paginate = array(
'Event' => array (...)
);
Or you can also set conditions and other keys in the $paginate array inside your action.
$this->paginate = array(
'conditions' => array(' ... '),
'limit' => 10
);
$data = $this->paginate('Event');
http://book.cakephp.org/2.0/en/controllers.html
http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html
R u using $name = 'Event' in your controller ?
If we wont mention model name in $this->paginate() , it will use model as mentioned in $name otherwise look in var $uses array and in that will get Model name (first one )
for e.g var $uses = array('Model1','Model2'); // $name != mentioned
n you want pagination with respect to Model2 then you have to specify ModelName in paginate array like $this->paginate('Model2') otherwise Model1 will be considered in pagination.

Resources