Cake PHP 3 needs limit option for find all method - cakephp

Inside a cell I need to access the TreeOptions model.
So I've wrote this :
$this->loadModel( 'TreeOptions' );
$i = $this->TreeOptions->find( 'all' );
But when I do the foreach like this :
foreach( $i as $row )
debug( $row->description );
It only returns the last record of the result.
The only way I've found to make it work as desired is adding the limit clause :
$i = $this->TreeOptions->find( 'all', [ 'limit' => 200 ] );
And then, I can get the whole set of records.
What am I missing ?
Thanks.
Regards.

In your first snippet, the variable $i, is a state where the query has not yet run. See the excerpt from CakePHP 3 Cookbook: Retrieving Data & Results — Using Finders to Load Data:
// Find all the articles.
// At this point the query has not run.
$query = $articles->find('all');
// Iteration will execute the query.
foreach ($query as $row) {
}
// Calling all() will execute the query
// and return the result set.
$results = $query->all();
// Once we have a result set we can get all the rows
$data = $results->toArray();
// Converting the query to an array will execute it.
$results = $query->toArray();

Related

CakePHP 3 - unable to generate a query with WHERE...OR conditions

CakePHP 3.7
I'm trying to generate a query which uses a WHERE...OR pattern. The equivalent in MySQL - which executes and gives the results I want is:
SELECT * FROM groups Groups WHERE (regulation_id = 1 AND label like '%labelling%') OR (id IN(89,1,8,232,228,276,268,294));
I've read the Advanced Conditions (https://book.cakephp.org/3.0/en/orm/query-builder.html#advanced-conditions) part of the documentation but can't generate that query.
Assume the Table class is Groups I have this:
$Groups = TableRegistry::getTableLocator()->get('Groups');
$groups_data = $Groups->find('all')->where(['regulation_id' => 1);
$groups_data = $groups_data->where(['label LIKE' => '%labelling%']);
This produces the first segment of the WHERE statement, i.e.
SELECT * FROM groups Groups WHERE (regulation_id = 1 AND label like '%labelling%')
However I can't see how to attach the OR condition, especially since orWhere() is deprecated.
So I've tried this - which is even given as an example in the docs:
$in_array = [89,1,8,232,228,276,268,294]; // ID's for IN condition
$groups_data = $groups_data->where(['OR' => ['id IN' => $in_array]]);
But this just appends an AND to the inside of my existing SQL:
SELECT * FROM groups Groups WHERE (regulation_id = 1 AND label like '%labelling%' AND id IN(89,1,8,232,228,276,268,294);
Which does not yield the correct results as the syntax isn't what's required to run this query.
How do you "move out" of the WHERE and append an OR condition like in the vanilla query?
I made several attempts using QueryExpression as per the docs, but all of these produced PHP Fatal Errors saying something to do with the Table class - I doubt this was on the right lines anyway.
"moving out" is a little tricky, you have to understand that internally the conditions are pushed into a \Cake\Database\Expression\QueryExpression object which by default uses AND to concatenate the statements, so whatever you push on to that, will be added using AND.
When you create OR statements, being it implicitly with the shown nested array syntax, or explicitly by using the expression builder, this creates a separate, self-contained expression, where its parts are being concatenated using OR, it will compile itself (and since there's only one condition, you don't see any OR's), and the result will be used in the parent expression, which in your case is the main/base expression object for the queries where clause.
Either pass the whole thing at once (being it via array syntax or expressions), eg:
$groups_data->where([
'OR' => [
'AND' => [
'regulation_id' => 1,
'label LIKE' => '%labelling%'
],
'id IN' => $in_array
]
]);
and of course you could build that array dynamically if required, or, if you for some reason need to use separate calls to where(), you could for example overwrite the conditions (third parameter of where()), and include the current ones where you need them:
$groups_data->where(
[
'OR' => [
$groups_data->clause('where'),
'id IN' => $in_array
]
],
[],
true
);
I know this issue is old but maybe someone is looking. Here is my solution:
protected $_hardValues= array(
'company_id' => $company_from_session;
);
function beforeFind($event=null, $query = null, $options = null, $primary = true){
$conds = [];
$columns = $this->getSchema()->columns();
foreach( $this->_hardValues as $field => $value){
if( !is_null($value) && in_array($field, $columns) ){
$conds[$this->_alias . '.' . $field] = $value;
}
}
if( empty( $conds)) return true;
$where = $query->clause('where'); //QueryExpression object;
if( empty( $where)){
$query->where($conds);
}else{
$where->add($conds);
}
}
As of CakePHP 4.x, the documented way of doing this is:
$query = $articles->find()
->where([
'author_id' => 3,
'OR' => [['view_count' => 2], ['view_count' => 3]],
]);
See documentation

Cakephp 3 : How to get max amout row from a table

I have table call users , like
id name amount created
1 a 100 6-16-2016
2 b 200 5-16-2016
I need max amount full row, I have tried below code but getting syntax error.
$user = $this->Users->find('all',[
'fields' => array('MAX(Users.amount) AS amount'),
]);
simplest way
$user = $this->Users->find('all',[
'fields' => array('amount' => 'MAX(Users.id)'),
]);
using select instead of an options array
$user = $this->Users->find()
->select(['amount' => 'MAX(Users.id)']);
making use of cake SQL functions
$query = $this->Users->find();
$user = $query
->select(['amount' => $query->func()->max('Users.id')]);
the above three all give the same results
if you want to have a single record you have to call ->first() on the query object:
$user = $user->first();
$amount = $user->amount;
Simplest method using CakePHP 3:
$this->Model->find('all')->select('amount')->hydrate(false)->max('amount')
Will result in an array containing the maximum amount in that table column.
Is is better to disable hydration before getting the results, to get the results as an array, instead of entity. Below is a complete example of how to get the results in an array, with distinct steps:
//create query
$query = $this->Users->find('all',[
'fields' => array('amount' => 'MAX(Users.id)'),
]);
$query->enableHydration(false); // Results as arrays instead of entities
$results = $query->all(); // Get ResultSet that contains array data.
$maxAmount = $results->toList(); // Once we have a result set we can get all the rows

Cakephp 2.5 subtract count from unset array operation

I am using CakePHp 2.5 and need to remove some results from the query:
$this->paginate = $paginate;
$results = $this->paginate('services');
foreach($results as $key=>$data )
{
if( empty( $dado['services']['service_id'] ) )
{
unset($results[$key]);
}
The result count will keep the original count.
Is there a way to subtract, the paginate query count, when i do unset the query results?
Looking at the Paginator class can not see if there is a property with the result count information.
Don't post-process the results of an sql call.
If you post-process the results of the sql call, unless the results fit in one page the count is always going to be inaccurate - because you'd be removing results from the current page, yet other results you would remove would still be present in the other pages - affecting the count.
Make paginate return what you want
Instead, just make the database give you the results you want, and none you don't:
$this->paginate['conditions'] = ['service_id IS NOT NULL'];
$results = $this->paginate('services');
If service id can be null or 0, you can account for that using a greater-than comparison:
$this->paginate['conditions'] = ['service_id >' => 0];
$results = $this->paginate('services');

Saving multiple rows in a single query

Is there anyway to have cake do a multi-row insert in a single query without writing raw SQL to do this? The saveMany and saveAssociated options will only save multiple rows in a single transaction, but that transaction contains multiple insert statements so these methods are clearly not a solution to write heavy applications.
Thanks for reading.
Yes
Though it's not a common practice to do so in app-land code, and doing so removes the possibility to use almost any application logic (validation rules, behaviors, events etc.). You can see an example of doing this in the way fixtures are loaded:
$db = ConnectionManager::getDataSource('default');
$table = "stuffs";
$fields = array('id', 'name');
$values = array(
array(1, 'one'),
array(2, 'two'),
...
);
$result = $db->insertMulti($table, $fields, $values);
You may also find this repository useful (either directly or as a basis for your code) which loads fixture files into your app database - using multi-inserts.
Yes, Big_Data is good idea for inserting bulk. But as AD7six note, it still use basic value quoting and does not return insert ids. And base on your ideas, i wrote small script to inserting bulk in a single query, using default CakePHP quoting and returning ids of inserted records.
$count = count($records);
$dbSource = $this->getDataSource();
$table = $dbSource->fullTableName($this->table);
$fields = $dbSource->prepareFields($this, array('fields' => array_keys($records[0])));
$values = array();
foreach ($records as $index => $record) {
if (!is_array($record) || !$record) {
return null;
}
foreach ($record as $column => $value) {
$values[$index][$column] = $dbSource->value($value, $this->getColumnType($column));
}
$values[$index] = '(' . implode(',', $values[$index]) . ')';
}
$query = 'INSERT INTO %s (%s) VALUES %s;';
$query = sprintf($query, $table, implode(',', $fields), implode(',', $values));
if (!$dbSource->execute($query)) {
return false;
}
$lastInsertId = $dbSource->getConnection()->lastInsertId();
$insertIds = array();
for ($i = 0; $i < $count; $i++) {
$insertIds[] = $lastInsertId + $i;
}
return $insertIds;
Someone pointed me towards the Big Data Behavior https://github.com/jmillerdesign/CakePHP_Big_Data
If you are using CakePHP 3.0 you can check the answer to this question: How to use insert in query builder insert multiple records?
If you are using CakePHP 2 you will have to use raw SQL like this:
$sql = "INSERT INTO `people` (`name`,`title`) VALUES ";
foreach($people as $person){
list($name,$title) = $person;
$sql.= "('$name','$title'),";
}
$this->query(substr($sql,0,-1));
Source: Inserting Multiple Rows with CakePHP 3
yes you can use like below
The getDataSource() method is static in CakePHP 2.x, so you should be able to use:
$db = ConnectionManager::getDataSource('default');
$db->rawQuery($some_sql);
here i am posting method to do. you have to create some SQL statement manually to insert multiple row in one time.
Please let me know if i can help you more.

Foreach logic issue with CakePHP

I have I problem that I hope someone can help me with. I thought this code was right, but it will not work. Below is my code, it is a function for my CakePHP 2.2.2 site, the main aim of the code is to produce a menu system from database results. The problem is with my foreach loop, it will not loop. All $Key does is return the value of 2 (three records within the table at this time). So When I display / echo / debug $Menu, the only result I get is the last result stored within the database.
I know the SQL command is right, if that is debuged / echoed then all three results are displayed. The idea of this loop was to get it to count the results, so that I could run a check on a selected field. Where I am going wrong?
function MenuSystem() {
$this->loadModel('Menu');
$MenuSQL = $this->Menu->find('all', array('conditions' => array('active' => true)));
foreach ($MenuSQL as $Key=>$Value) {
$MenuSystem = $MenuSQL[$Key];
$this->Menu = $MenuSystem;
}
}
Many Thanks,
Glenn.
UPDATE :::
Below is my function, now my foreach loop now works, don't know what I was doing wrong, but I know think its working. You can see the print_r command that I am using for testing, if I use that, then all links from my database are printed / echoed on the screen and all works. But if I try and call the $this->Menu from another controller, then only the last record is echoed on the screen. I have moved the $this->Menu outside of the foreach loop, but it made no difference, with it inside the loop or outside, it still only echoes the last record and not all three. So what I am doing wrong?
function MenuSystem() {
$this->loadModel('Menu');
$SiteBase = '/projects/cake/';
$MenuSQL = $this->Menu->find('all', array('conditions' => array('active' => true)));
foreach ($MenuSQL as $key => $Value) {
$MenuAccessLevel = $MenuSQL[$key]['Menu']['roles_id'];
if ($MenuAccessLevel == 1) {
$Path = $MenuSQL[$key]['Menu']['path'];
$Title = $MenuSQL[$key]['Menu']['title'];
$MenuSys = "<a href=\" " . $SiteBase . $Path . " \">" . $Title ."";
} else {
print ("Admin");
}
//print_r($MenuSys);
} //End of Foreach Loop
$this->Menu = $MenuSys;
} //End of function MenuSystem
So When I display / echo / debug $Menu, the only result I get is the last result stored within the database.
You're setting the value of $this->Menu within the foreach, so when the foreach is complete it will take the last value iterated over.
If you want to find the number of records matching a condition, try:
$menuCount = $this->Menu->find('count', array(
'conditions'=>array('active'=>true)
));
$this->set(compact('menuCount'));
Edit: also, by setting the value of $this->Menu within the foreach, you're overwriting the Menu model variable. This is not a good idea.
Edit2: to get the counts of rows as grouped by some value, try:
$this->Menu->virtualFields = array('count' => 'count(*)');
$counts = $this->Menu->find('all', array(
'group'=>'Role',
'fields'=>array('Role', 'count'),
));
This generates SQL to have the results grouped by the Role column. Returned fields are the name of the role, and the number of rows having that value.
If you wanted to do it with a foreach loop instead, it might look like:
$menus = $this->Menu->find('all', array('fields'=>array('id', 'Role')));
$counts = array('user'=>0, 'admin'=>0);
foreach ($menus as $menu) {
$role = $menu['Menu']['Role'];
$counts[$role] += 1;
}

Resources