cakephp find('all') not returning properly - cakephp

So here is my latest issue:
I run this query in my Cakephp controller:
$acctRenewLast2Entries = $this->AccountRenew->find
(
'all',
array
(
'conditions' => array('Acc_Id' => $plan["Account"]["Acc_Id"]),
'order' => array('AccR_Id' => 'DESC')
)
);
I am expecting 4 records for this SQL statement. Instead, on running Debug in my controller, this is what I get for each row for above (see first record):
app/controllers/admins_controller.php (line 2584)
1
app/controllers/admins_controller.php (line 2584)
Array
(
[AccountRenew] => Array
(
[AccR_Id] => 470
[AccR_Date] => 2012-06-23 01:21:11
[AccR_Hstart_Date] => 2012-06-23 01:21:11
[AccR_Hend_Date] => 2012-08-23 01:21:11
[AccR_End_Date] => 2013-08-23 01:21:11
[AccR_Status] => PAID
[AccR_Reason] => RENEWAL
[Inv_Id] => 467
[Inac_Id] =>
[Acc_Id] => 196
[AccT_Id] => 44
[Amount] => 16
[AccP_Id] => 0
)
)
app/controllers/admins_controller.php (line 2584)
Array
(
[AccountRenew] => Array
(
[AccR_Id] => 465
[AccR_Date] => 2012-06-23 01:17:35
[AccR_Hstart_Date] => 2012-06-23 01:17:35
[AccR_Hend_Date] => 2012-07-23 01:17:35
[AccR_End_Date] => 2012-07-23 01:17:35
[AccR_Status] => PAID
[AccR_Reason] => RENEWAL
[Inv_Id] => 462
[Inac_Id] =>
[Acc_Id] => 196
[AccT_Id] => 41
[Amount] => 16
[AccP_Id] => 0
)
)
app/controllers/admins_controller.php (line 2584)
Array
(
[AccountRenew] => Array
(
[AccR_Id] => 269
[AccR_Date] => 2012-06-06 10:15:56
[AccR_Hstart_Date] => 2012-06-06 17:15:56
[AccR_Hend_Date] => 2012-06-20 17:15:56
[AccR_End_Date] => 2012-06-20 10:15:56
[AccR_Status] => TRIAL
[AccR_Reason] =>
[Inv_Id] => 0
[Inac_Id] =>
[Acc_Id] => 196
[AccT_Id] => 0
[Amount] => 0
[AccP_Id] => 0
)
)
Now, when I run sql_dump, I get the following query that was run :
SELECT `AccountRenew`.`AccR_Id`, `AccountRenew`.`AccR_Date`, `AccountRenew`.`AccR_Hstart_Date`, `AccountRenew`.`AccR_Hend_Date`, `AccountRenew`.`AccR_End_Date`, `AccountRenew`.`AccR_Status`, `AccountRenew`.`AccR_Reason`, `AccountRenew`.`Inv_Id`, `AccountRenew`.`Inac_Id`, `AccountRenew`.`Acc_Id`, `AccountRenew`.`AccT_Id`, `AccountRenew`.`Amount`, `AccountRenew`.`AccP_Id` FROM `account_renews` AS `AccountRenew` WHERE `Acc_Id` = 196 ORDER BY `AccR_Id` DESC 4 4
And when I run the above query in MySQL, I do get all my 4 records, including the first one which in the array appears as 1 (way up top of my write-up).
I sincerely hope someone out there can help, because I spent the last 1.5 days without any luck as to why MySQL pulls up the complete set, but Cake only seems to retrieve the last 3, and replace the first record with an array of "1".
Thanks in advance!

#user996302 this issue seems remotely connected to one of my Cake Lighthouse tickets you pointed out.
#GMOAdmin I suspect that there could be a problem with the name of your model as the word "renew" is a verb and since it has no plural form this could somehow be obstructing the CakePHP conventions as the Inflector class may not be able to translate this.
The correct noun is renewal and it's plural form: renewals. You can try renaming (according to the convetions) the DB table, Model - name and classname, Controller - name and classname and see if that works.
You can test if Inflector is handling this correctly via the following Inflector methods:
Inflector::pluralize($singular), Inflector::classify($tableName), Inflector::tableize($camelCase)
A quick fix would be to issue the working query with $this->ModelName->query($queryToRun); this way you can go around this since, as you say, the query runs correctly when ran over the DB. Overall this is truly and interesting issue and I suggest you have the CakeCore team look at it - if it is reproducable then this is a BUG and it need fixin.

Very strange problem.
I don't know what's happening, but I read recently a bug report which sounds a lot like your problem: ticket.
You may try as suggested by the author: put the param $showHTML (cake docs) as true:
debug($var, true, true);
Hope that helps.

Okay, I think I have found the answer. At least, this was causing the problems for me.
The CakePHP counterCache
What I had:
public $hasMany = array(
'Flight' => array(
'className' => 'Flight',
'foreignKey' => 'user_plane_id',
'counterCache' => true
)
);
Very stupid, but it did cause the problems. So perhaps you are having a counterCache as well? Or maybe you have set another key in the relationships wrong?
If you don't see the problem at my code snippet. I should have added the counterCache to the $belongsTo instead of the $hasMany. So it would be something like this:
public $belongsTo = array(
'UserPlane' => array(
'className' => 'UserPlane',
'foreignKey' => 'user_plane_id',
'counterCache' => true
)
);
note: I am running CakePHP 2.x and not 1.3!

Related

Cakephp 3 Append $this->request->data using array() - Indirect modification of overloaded property data has no effect

I have been trying and trying a lot but i just don't understand how to achieve this .. Here is what i want to achieve by the way..
I am using cakephp 3.0 and i am trying to append $this->request->data by trying below code in my controller...
$this->data['Leaveregisters'] = array(
'name'=>'another',
'surname'=>'whatever'
);
echo '<pre>';
print_r($this->request->data);
exit;
but i am keep getting below error
Notice (8): Indirect modification of overloaded property App\Controller\LeaveregistersController::$data has no effect
However, i am able to append data using below code
$this->request->data['name'] = 'another';
$this->request->data['surname'] = 'whatever';
But i am just wondering why i am not able to achieve the same thing using array() or what should i do to achieve this ?
Array should be appened in the same array as well if i do $this->request->data currently if i use below code
$this->request->data('Leaveregisters', [
'name' => 'another',
'surname' => 'whatever asdfa'
]);
echo '<pre>';
print_r($this->request->data);
exit;
Its getting appeneded but with new key like below which i do not want
Array
(
[leaveregister_user_id] => 2
[leaveregister_num_leaves] => 15
[leaveregister_sdate] => 2016-09-01
[leaveregister_edate] => 2016-09-22
[leaveregister_leave_type] => 1
[leaveregister_status] => 1
[leaveregister_created_date] => 2016-09-20 14:54:29
[leaveregister_modified_date] => 2016-09-20 14:54:29
[Leaveregisters] => Array
(
[name] => another
[surname] => whatever asdfa
)
)
I want something like this instead...
Array
(
[leaveregister_user_id] => 2
[leaveregister_num_leaves] => 15
[leaveregister_sdate] => 2016-09-01
[leaveregister_edate] => 2016-09-22
[leaveregister_leave_type] => 1
[leaveregister_status] => 1
[leaveregister_created_date] => 2016-09-20 14:54:29
[leaveregister_modified_date] => 2016-09-20 14:54:29
[name] => another
[surname] => whatever asdfa
)
So that i can just save my data using $this->request->data
Can someone guide me for the same ?
Thanks
There is no Controller::$data property anymore in CakePHP 3.
Cookbook > Appendices > 3.X Migration Guide > 3.0 Migration Guide > Controller
The only correct way is to access the request object, and of course you can set nested data if you want to the same way, ie:
$this->request->data['Leaveregisters'] = [
'name' => 'another',
'surname' => 'whatever'
];
Alternatively use the data() method:
$this->request->data('Leaveregisters', [
'name' => 'another',
'surname' => 'whatever'
]);
See also
Cookbook > Request & Response Objects > Request Body Data
API > \Cake\Network\Request::data()

CakePHP: how to get total number of records retrieved with pagination

When you retrieve records using $this->paginate('Modelname') with some page limit, how do you get the total number of records retrieved?
I'd like to display this total count on the view, but count($recordsRetrieved) returns the number displayed only on the current page. So, if total number of records retrieved is 99 and limit is set to 10, it returns 10, not the 99.
You can debug($this->Paginator->params());
This will give you
/*
Array
(
[page] => 2
[current] => 2
[count] => 43
[prevPage] => 1
[nextPage] => 3
[pageCount] => 3
[order] =>
[limit] => 20
[options] => Array
(
[page] => 2
[conditions] => Array
(
)
)
[paramType] => named
)
*/
The final code for PHP >=5.4:
$this->Paginator->params()['count'];
For PHP versions less than 5.4:
$paginatorInformation = $this->Paginator->params();
$totalPageCount = $paginatorInformation['count'];
To get your answer go to he following link
http://book.cakephp.org/2.0/en/controllers/request-response.html
use pr($this->request->params) you will find all the stuff of pagination
For cake 3.x
you can use method getPagingParams
example:
debug($this->Paginator->getPagingParams());
output:
[
'users' => [
'finder' => 'all',
'page' => (int) 1,
'current' => (int) 5,
'count' => (int) 5,
'perPage' => (int) 1,
'prevPage' => false,
'nextPage' => true,
'pageCount' => (int) 5,
'sort' => null,
'direction' => false,
'limit' => null,
'sortDefault' => false,
'directionDefault' => false,
'scope' => null
]
]
Here's how I got the count from my controller*
This is for CakePHP 2.3. It uses a view helper, which is typically a no-no in the controller as it violates MVC, but in this case I think makes sense to keep the code DRY.
// Top of file
App::uses('PaginatorHelper', 'View/Helper');
// Later in controller method
$paginatorHelper = new PaginatorHelper(new View(null));
$records = $this->paginate();
$count = $paginatorHelper->params()['count'];
* I know the OP asked about from the view, but I figure if Arzon Barua's answer is helping people (though I think it only tells you the requested count, not the actual count as the OP wants), then this might help too.
This way is getting a pagination information over controller.
class YourController extends Controller{
$helpers = array('Paginator');
public fn(){
$view = new View(null);
var_dump($view->paginator->params());
}
}

Cakephp contain and belongsTo hasMany conditions

From the CakePHP manual:
Let's say you had a belongsTo relationship between Posts and Authors. Let's say you wanted to find all the posts that contained a certain keyword ("magic") or were created in the past two weeks, but you want to restrict your search to posts written by Bob:
array (
"Author.name" => "Bob",
"OR" => array (
"Post.title LIKE" => "%magic%",
"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
)
However, its not working for me. I've got Scrapes belongsTo Vargroup, and Vargroup hasMany Scrape. My scrapes table has a field vargroup_id
When I do:
$this->Vargroup->contain('Scrape');
$this->Vargroup->find('first');
I get:
Array
(
[Vargroup] => Array
(
[id] => 16950
[item_id] => 1056
[image] => cn4535d266.jpg
[price] => 22.95
[instock] => 1
[timestamp] => 2012-10-29 11:35:10
)
[Scrape] => Array
(
[0] => Array
(
[id] => 18163
[item_id] => 1056
[vargroup_id] => 16950
[timestamp] => 2012-05-23 15:24:31
[instock] => 1
[price] => 22.95
)
)
)
But when I do :
$this->Vargroup->contain('Scrape');
$this->Vargroup->find('first', array(
'conditions' => array(
'not' => array(
array('Scrape.timestamp >' => $today)
)
)
));
Im getting The following error with associated sql output
Warning (512): SQL Error: 1054: Unknown column 'Scrape.timestamp' in 'where clause'
Query: SELECT `Vargroup`.`id`, `Vargroup`.`item_id`, `Vargroup`.`image`, `Vargroup`.`price`, `Vargroup`.`instock`, `Vargroup`.`timestamp` FROM `vargroups` AS `Vargroup` WHERE `Scrape`.`timestamp` = '2012-10-29
It doesn't look like its binding the table at all..
Any help would be appreciated.
Thanks
Try with this:
$this->Vargroup->contain('Scrape');
$this->Vargroup->find('first', array(
'conditions' => array(
'not' => array(
'Scrape.timestamp >' => array($today)
)
)
));
i think this examples from cakephp page tell the right way to do a "not" condition,
check it out.
array(
"NOT" => array("Post.title" => array("First post", "Second post", "Third post"))
)
this is the complex find conditions page if you have more questions. Cakephp complex find conditions

Complex find query for hasMany relationship in cakePHP 1.3

I have been busy with the cakePHP framework for a couple of months now and I really love it. At the moment I'm working on a very new project and it does the job like it should (I think ...) but I feel uncomfortable with some code I wrote. In fact I should optimize my paginate conditions query so I get immediately the right results (right now I manipulate the result set by a bunch of Set::extract method calls.
I'll sketch the relevant aspects of the application. I have a model 'Site' who has a hasMany relationship with the model 'SiteMeta'. This last table looks as follow: id, site_id, key, value, created.
In this last model I record several values of the site at various periods. The name of the key I want to store (e.g. alexarank, google pagerank, ...), and off course also the value. At a given interval I let my app update this database so I can track evolution of this values.
Now my problem is this.
On the overview page of the various websites (controller => Sites, action => index) I'd like to show the CURRENT pagerank of the website. Thus I need one exact SiteMeta record where the 'created' field is the highest and the value in 'key' should be matching the word 'pagerank'. I've tried several things I read on the net but got none of them working (containable, bindmodel, etc.). Probably I'm doing something wrong.
Right now I get results like this when I do a $this->paginate
Array
(
[0] => Array
(
[Site] => Array
(
[id] => 1
[parent_id] => 0
[title] => test
[url] => http://www.test.com
[slug] => www_test_com
[keywords] => cpc,seo
[language_id] => 1
)
[SiteMeta] => Array
(
[0] => Array
(
[id] => 1
[site_id] => 1
[key] => pagerank
[value] => 5
[created] => 2010-08-03 00:00:00
)
[1] => Array
(
[id] => 2
[site_id] => 1
[key] => pagerank
[value] => 2
[created] => 2010-08-17 00:00:00
)
[2] => Array
(
[id] => 5
[site_id] => 1
[key] => alexa
[value] => 1900000
[created] => 2010-08-10 17:39:06
)
)
)
To get the pagerank I just loop through all the sites and manipulate this array I get. Next I filter the results with Set::extract. But this doens't feel quite right :)
$sitesToCheck = $this->paginate($this->_searchConditions($this->params));
foreach($sitesToCheck as $site) {
$pagerank = $this->_getPageRank($site['Site']);
$alexa = $this->_getAlexa($site['Site']);
$site['Site']['pagerank'] = $pagerank;
$sites[] = $site;
}
if (isset($this->params['named']['gpr']) && $this->params['named']['gpr']) {
$rank = explode('-', $this->params['named']['gpr']);
$min = $rank[0];$max = $rank[1];
$sites = Set::extract('/Site[pagerank<=' . $max . '][pagerank>=' . $min .']', $sites);
}
$this->set(compact('sites', 'direction'));
Could you guys please help me to think about a solution for this? Thanks in advance.
Thanks for the contributions. I tried these options (also something with bindmodel but not working also) but still can't get this to work like it should be. If I define this
$this->paginate = array(
'joins'=> array(
array(
'table'=>'site_metas',
'alias'=>'SiteMeta',
'type' =>'inner',
'conditions' =>array('Site.id = SiteMeta.site_id')
)
),
);
I get duplicate results
I have a site with 3 different SiteMeta records and a site with 2 different record.
The paginate method returns me 5 records in total. There's probably an easy solution for this, but I can't figure it out :)
Also I tried to write a sql query myself, but seems I can't use the pagination magic in that case. Query I'd like to imitate with pagination options and conditions is the following. The query returns exactly as I would like to get.
$sites = $this->Site->query('SELECT * FROM sites Site, site_metas SiteMeta WHERE SiteMeta.id = (select SiteMeta.id from site_metas SiteMeta WHERE Site.id = SiteMeta.site_id AND SiteMeta.key = \'pagerank\' order by created desc limit 0,1 )');
As you are trying to retrieve data in a hasMany relationship, cakephp doesn't join the tables by default. If you go for joins you can do something like:
$this->paginate = array(
'joins'=>array(
array(
'table'=>'accounts',
'alias'=>'Account',
'type' =>'inner',
'conditions' =>array('User.id = Account.user_id')
)
),
'conditions'=> array('OR' =>
array(
'Account.name'=>$this->params['named']['nickname'],
'User.id' => 5)
)
);
$users = $this->paginate();
$this->set('users',$users);
debug($users);
$this->render('/users/index');
You have to fit this according to your needs of course. More on joins, like already mentioned in another answer.
Edit 1: This is because you are missing the second 'conditions'. See my code snippet. The first 'conditions' just states where the join happens, whereas the second 'conditions' makes the actual selection.
Edit 2: Here some info on how to write conditions in order to select needed data. You may want to use the max function of your rdbms on column created in your refined condition.
Edit 3: Containable and joins should not be used together. Quoted from the manual: Using joins with Containable behavior could lead to some SQL errors (duplicate tables), so you need to use the joins method as an alternative for Containable if your main goal is to perform searches based on related data. Containable is best suited to restricting the amount of related data brought by a find statement. You have not tried my edit 2 yet, I think.
Edit 4: One possible solution could be to add a field last_updated to the table Sites. This field can then be used in the second conditions statement to compare with the SiteMeta.created value.
Try something like this:
$this->paginate = array(
'fields'=>array(
'Site.*',
'SiteMeta.*',
'MAX(SiteMeta.created) as last_date'
),
'group' => 'SiteMeta.key'
'conditions' => array(
'SiteMeta.key' => 'pagerank'
)
);
$data = $this->paginate('Site');
Or this:
$conditions = array(
'recursive' => 1,
'fields'=>array(
'Site.*',
'SiteMeta.*',
'MAX(SiteMeta.created) as last_date'
),
'group' => 'SiteMeta.key'
'conditions' => array(
'SiteMeta.key' => 'pagerank'
)
);
$data = $this->Site->find('all', $conditions);
If that does not work check this and this. I am 100% sure that it is possible to get the result you want with a single query.
Try something like this (with containable set up on your models):
$this->Site->recursive = -1;
$this->paginate = array(
'conditions' => array(
'Site.title' => 'title') //or whatever conditions you want... if any
'contain' => array(
'SiteMeta' => array(
'conditions' => array(
'SiteMeta.key' => 'pagerank'),
'limit' => 1,
'order' => 'SiteMeta.created DESC')));
I use containable so much that I actually have this in my app_model file so it applies to all models:
var $actsAs = array('Containable');
Many thinks to all who managed to help me through this :)
I got it fixed after all hehe.
Eventually this has been the trick for me
$this->paginate = array(
'joins'=> array(
array(
'table'=>'site_metas',
'alias'=>'SiteMeta',
'type' =>'inner',
'conditions' => array('Site.id = SiteMeta.site_id'))
),
'group' => 'Site.id',
'contain' => array(
'SiteMeta' => array(
'conditions' => array(
'SiteMeta.key' => 'pagerank'),
'limit' => 1,
'order' => SiteMeta.created DESC',
)));
$sites = $this->paginate();

saveall not saving associated data

I'm having a problem trying to save (update) some associated data. I've read about a million google returns, but nothing seems to be the solution. I'm at my wit's end and hope some kind soul here can help.
I'm using 1.3.0-RC4, my database is in InnoDB.
Course has many course_tees
CourseTee belongs to course
My controller function is pretty simple (I've made it as simple as possible):
if(!empty($this->data))
$this->Course->saveAll($this->data);
I've tried a lot of different variations of that $this->data['Course'], save($this->data), etc without luck.
It saves the Course info, but not the CourseTee stuff. I don't get an error message.
Since I don't know how many tees any given course will have, I generate the form inputs dynamically in a loop.
$form->input('CourseTee.'.$i.'.teeName', array(
'error' => false,
'label' => false,
'value'=>$data['course_tees'][$i]['teeName']
))
The course inputs are simpler:
$form->input('Course.hcp'.$j, array(
'error' => false,
'label' => false,
'class' => 'form_small_w',
'value'=>$data['Course']['hcp'.$j]
))
And this is how my data is formatted:
Array
(
[Course] => Array
(
[id] => 1028476
...
)
[CourseTee] => Array
(
[0] => Array
(
[key] => 636
[courseid] => 1028476
...
)
[1] => Array
(
[key] => 637
[courseid] => 1028476
...
)
...
)
)
According to CakePHP conventions you must provide [course_id] => 1028476 and not [courseid] => 1028476. Check you model bindings as well (capitalizations and underscores). There must be "Course has many CourseTee".
Do this way to save:
if ($this->Course->saveAll($this->data, array('validate' => 'first'))) {
$this->_flash(__('Successfully saved.', true), 'success');
} else {
$this->_flash(__('Cannot save. Does not validates.', true), 'error');
}

Resources