cakephp limit results find('all') - cakephp

I just want to show on index.ctp all results from a model wich are true as conditions. Like this select:
select * from revistas where isonline = 'true';
Ive tried this code below on controller:
public function index() {
//$iscond = $this->Revista->findAllByNome('testa');
//$this->Revista->query("SELECT id, nome, acronimo, modified, user_id from xconv.revistas where isonline = 't'");
$this->Revista->find('all', array(
'contain' => array(
'conditions' => array('Revista.isonline' => 't'))));
$this->Revista->recursive = 0;
$this->set('revistas', $this->paginate());
}
Those 2 comments above tried b4. It doesn return any errors, but dont do the condition.
Is there any other file of the MVC to write more code ? What went wrong ??
Ty

TLDR:
You need to supply your conditions to the actual pagination as opposed to doing a find() query, then doing a completely separate $this->paginate().
Explanation:
$this->paginate(); will actually run a query for you. So - basically, in your question, you're running a query for no reason, then doing paginate() which runs another query that has no conditions.
Instead, apply the condition like this:
$this->Paginator->settings = array('conditions'=>array('Revista.isonline' => 't'));
$this->paginate();
Note: As noted by your comment and another answer, there are multiple ways to apply conditions to paginate - the main point to this answer is pointing out that you need to apply the conditions to the paginate, not a previous query.

Using your clues at #Dave's answer comments, I think this is a solution with the "paginator filter",
public function index() {
$this->Revista->recursive = 0; //your old code
$this->paginate['conditions']=array('Revista.isonline' => 't'); //new code
$this->set('revistas', $this->paginate()); //your old code
}

try this:
public function index() {
$this->Revista->find('all', array(
'conditions' => array('Revista.isonline' => 't')));
$this->Revista->recursive = 0;
$this->set('revistas', $this->paginate());
}

Related

CakePHP 3: Properly writing functions for the Entity Model

I have a blog model, and I suspect I'm not writing my code correctly to best take advantage of CakePHP's MVC structure.
Here are some snippets from my Posts controller.
public function view() {
$posts = $this->Posts->find('all')->contain([
'Comments' => ['Users'],
'Users'
]);
$this->set(compact('posts'));
}
public function index() {
$post = $this->Posts->find('all')->contain([
'Users'
])->limit(20);
$this->set(compact('post'));
}
This is a snippet from the index.ctp template
foreach ( $posts as $post ) {
<div class="post">
<h1 class="title>
<?= h($post->title) ?>
<small><?php echo $post->getCommentCount(); ?> comments</small>
</h1>
<div class="body">
<?= h($post->body) ?>
</div>
</div>
}
In my Post Entity, I have the following function
public function getCommentCount(){
$count = 0;
foreach ($this->comments as $comment){
if ( $comment->isPublished() ) {
$count += 1;
}
}
return $count;
}
My problem is I need to call the getCommentCount function from the index.ctp where the $posts object has no comment children (which the function uses).
Am I misunderstanding how the Entity functions should be coded? Instead of accessing expected variables of this object which sometimes aren't there, should I be querying the database from the Entity? Is there another approach I should be doing?
Am I misunderstanding how the Entity functions should be coded?
Yes, you do, because...
Instead of accessing expected variables of this object which sometimes aren't there, should I be querying the database from the Entity? Is there another approach I should be doing?
...an entity should be a dumb data object. When you fetch data from there you add business logic. Any data manipulation should happen somewhere in the model layer, or a service. Most of the time people using CakePHP put code into the table object, which is somewhat OKish. I'm creating additional classes in the model layer for different things and namespace them App\Model\SomeModule or directly in my app root App\SomeModule and inject whatever else I need there (request, tables...).
This code is also absolute inefficient:
public function getCommentCount(){
$count = 0;
foreach ($this->comments as $comment){
if ( $comment->isPublished() ) {
$count += 1;
}
}
return $count;
}
It assumes all comments were loaded
It is required to get all comments to get an accurate count
it iterates over all comments to filter published comments
It does it inside the entity
What if there are 500 comments? Why aren't you simply doing a count query on the database and filter them by their published status? But this would still require you to do one additional query per record. So for counts it is a lot more efficient and easy if you use the Counter Cache Behavior.
If you really need to manipulate data after you fetched it but before rendering it use map/reduce. You can refactor your getCommentCount() and use map/reduce instead if you would like to stick to your inefficient way of doing it. The linked documentation even contains an example for counting stuff.
In your index funcition you set $post, but in the index.ctp you use $posts, and you missed out to contain Comments relationship in your query.
I changed it for you:
public function index() {
$posts = $this->Posts->find('all')->contain([
'Users',
'Comments'
])
->limit(20);
$this->set(compact('posts'));
}
And based on your comment, you could get the total comments in a query like this:
public function index() {
$posts = $this->Posts->find();
$posts->select([
'total_comments' => $posts->func()->sum('Comments.id')
])
->select($this->Posts)
->contain([
'Users',
'Comments'
])
->limit(20);
$this->set(compact('posts'));
}
See Returning the Total Count of Records for more info.
You can do it this way:
$posts = $this->Posts->find('all' , [
'fields' => array_merge(
$this->Posts->schema()->columns(), [
'countCommentsAlias' => '(
SELECT COUNT(comments.id)
FROM comments
WHERE comments.post_id = Posts.id
)',
'Users.name',
'Users.email'
]
),
'contain' => ['Users']
])
->toArray();

Ignore Callbacks (beforeFind, beforeSave etc) in CakePHP 3.x

In CakePHP 2.x we have the find('all','callbacks'=>false)
What is an equivalent alternative in CakePHP3?
I have a situation where in my beforeFind callback (in my model's behavior) I'm appending a site_id to every query (for a multi-tenant app).
90% of the time I want this query appended via beforeFind, but %10 of the time I want my finds to ignore the callbacks.
I've looked at: Cakephp 3: How to ignore beforefind for specific queries? which comes close, but applying that method won't 'chain' the ignored beforeFind() callback on associated models, which I need it to do.
Updated code:
I've got two tables 'sites' and 'details' sites hasOne details, details belongs to sites. Inner Join.
In my AppController's initialize() function I've got
$tbl = TableRegistry::get( 'Sites' );
$options = [
'conditions' =>
['Sites.domain' => 'three.dev.mac',
'Sites.is_current' => 1,
'Sites.is_archive' => 0],
'contain' => 'Details', // this contain causes the problem
'ignoreCallbacks' => true
];
$tenant_details = $tbl->find('all',$options)->first();
My models beforeFind() behavior callback
public function beforeFind( Event $event, Query $query, ArrayObject $options ) {
debug($options);
if(isset($options['ignoreCallbacks']) && $options['ignoreCallbacks'] === true) {
// don't filter where clause
}
else{
// filter where clause by default
$query->where( [$this->_table->alias().'.'.'site_id'=> 7 ]);
}
return $query;
}
If I comment out the 'contain' line of my find call, the where query get's ignored as it should and my debug call returns 'ignoreCallbacks' => true' which is great.
If I leave the 'contain' line in the find() call (which is what I want) I get 2 debug outputs from beforeFind(), the first has 'ignoreCallbacks' => true', the second is empty. Apparently the second overrides the first and the query tries to append a site_id, which I don't want.
Any thoughts?
http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html
Any options that are not in this list will be passed to beforeFind listeners where they can be used to modify the query object. You can use the getOptions() method on a query object to retrieve the options used.
So just pass a custom option to your queries find() call as 2nd arg and read that option in the beforeFind() like described above.
if (isset($options['useSiteId']) && $options['useSiteId'] === true) { /*...*/ }
I've found a way to (although it seems ugly) to have the custom $options that are passed (as #burzum mentioned in his answer) in a find call to the associated table's beforeFind() method if using 'contain' in the find. Hope this helps someone who was experiencing the same issue.
$tbl = TableRegistry::get( 'Sites' );
$options = [
'conditions' =>
$conditions,
'contain' => [
'Details'=> function ($q) {
return $q->applyOptions(['ignoreCallbacks' => true]); // IMPORTANT this is required to send the 'ignoreCallbacks' option to the contained table.
}
],
'ignoreCallbacks' => true
];
$tenant_details_query = $tbl->find('all',$options)->first();

Running find('all') or find('first') on identical set of conditions in Cake 2

In a controller/index action in my CakePHP2 app, I have
$this->Model-find('all',array( ... lots conditions and things... ));
Then, in controller/view I have:
$this->Model-find('first',array( ... lots conditions and things... ));
Both of these work really well, but I'm obviously repeating a large chunk of code (namely, the find conditions, field list etc) with the only two differences being that in the first one I'm doing find('all') and in the second one I'm doing find('first') and providing the id as one of the conditions.
Is there a way that I can move the conditions into the model and then run different types of find on those conditions?
You can create a method in Youur Model:
public function getConditions($id = null) {
return array( ... lots conditions and things... );
}
somewhere in this method just test if (!empty($id)) to return correct result. (You haven't provide the 'conditions and things' so I can't write the exact code here...
and use it in both controler actions:
$this->Model->find('first', $this->Model->getConditions()));
$this->Model->find('all', $this->Model->getConditions()));
Yes you can move your query into model class as a function/method. see sample bellow for post model:
class Post extends AppModel {
public $name = 'Post';
public function getAll() {
return $this->find('all', array('limit' => 20, 'order' => 'Post.created DESC'));
}
}
and call it on your controller by $this->Model->getAll();
$this->Post->getAll();
You can also add parameter to your function.
Add the conditions as an attribute of your controller
class FoosController extends AppController{
public $conditions = array( ... lots conditions and things... )
public function index(){
$this-Foo->find('all', $this->conditions);
}
public function view(){
$this-Foo->find('first', $this->conditions);
}
}

CakePHP - access to associated model in beforeSave

In my app Quotes belongTo a product, which in turn belongs to a material. As I can't get the product model afterFind array to include the material when it is accessed from the Quote model I have associated the Quote directly with a material.
The problem I'm having now is that the material_id for the quote needs to be automatically saved based on the product which is selected for the quote
i.e. pulling the value of Product.material_id from the selected product and saving it to the Quote.material_id field automatically before the Quote has been saved to the database.
I'm quite new to cakePHP. Does anyone know how this can be done?
EDIT:
Here is an example to help explain. In my Quote model i can have:
public function beforeSave($options) {
$this->data['Quote']['material_id'] = 4;
return true;
}
but i need to do something more like this which doesn't work:
public function beforeSave($options) {
$this->data['Quote']['material_id'] = $this->Product['material_id'];
return true;
}
I'm shocked this hasn't been properly answered yet....
Oldskool's response is semi-correct, but not entirely right. The use of "$this->Quote" is incorrect, as the beforeSave function itself resides in the Quote class. I'll explain using an example.
-> We have a model Subscription which belongsTo a SubscriptionsPlan
-> Model SubscriptionsPlan hasMany Suscriptions
To access the SubscriptionsPlan data in a beforeSave function in the Subscription model, you would do the following:
public function beforeSave($options = array()){
$options = array(
'conditions' => array(
'SubscriptionsPlan.subscriptions_plan_id' => $this->data[$this->alias]['subscriptions_plan_id']
)
);
$plan = $this->SubscriptionsPlan->find('first', $options);
//REST OF BEFORE SAVE CODE GOES HERE
return true;
}
It should probably work by using a find instead.
public function beforeSave($options) {
// Assuming your Product model is associated with your Quote model
$product = $this->Quote->Product->find('first', array(
'conditions' => array(
'Product.material_id' => $this->data['Quote']['material_id']
)
));
$this->data['Quote']['material_id'] = $product['material_id'];
return true;
}

How to use one controller for more than one sql query in Cakephp?

I have a serious deadlock about my project. I am looking for a solution for days but there is nothing.
My index page have 4 different mysql queries. I tried to code this at first inside 1 controller and 1 view. It was not the right way. Then somebody suggested using 4 elements with requestAction() function. It was very good at first. Then I needed mysql between command. I could not found a way for it to use with requestAction(). My question on Cakephp group still unanswered. Somebody suggested using actions in controller. But I couldn't figure it out how to create that controller.
Please tell me what you know about it. Thanks in advance,
Here is my current post controller's index function:
function index() {
$posts = $this->paginate();
if (isset($this->params['requested'])) {
return $posts;
} else {
$sql = array( 'conditions' => array( 'id BETWEEN ? AND ?' => array( 286, 291 ) ) );
$this->Post->find('all', $sql);
$this->set('posts', $posts);
}
}
What should I do? Should I add a new controller for rest 3 actions? Or should I do something inside index() function?
Every time I need several queries in the single controller I do this way:
// model Foo
function edit($id){
$this->data = $this->Foo->read(null, $id);
$second = $this->Foo->Foo2->find('list');
$third = $this->Foo->Foo3->find('list');
$this->set(compact('second', 'third'));
}
I guess, you want to paginate on those 3 columns so the example above is not good for that.
I think you have an error in your method. $posts is not related to your find. There should be $posts = $this->Post->find('all', $sql);. But this would not allow you to paginate on the result. Take a look at Custom Query Pagination in the manual. Maybe this would work for you:
function index(){
if(!isset($this->params['requested'])) {
$this->paginate = array('Post' => array(
'conditions' => array('id BETWEEN ? AND ?' => array(286, 291))
));
}
$this->set('posts', $this->paginate());
}

Resources