Array structure of raw SQL query results in CakePHP 3.8 / CakePHP 4 vs CakePHP 2 - cakephp

After CakePHP 4.0 has been released, we are considering to migrate our CakePHP 2.x application to 3.8 or 4.0. Currently, we are stuck with this issue:
Our application uses raw SQL statements sometimes using the Model::query() method.
For example, this CakePHP 2 code:
$sql = "SELECT u.id, u.firstname FROM users u, contacts c WHERE u.id = 2 and c.id = u.contact_id";
$u = $this->User->query($sql); // Or on any other model...
$this->log($u);
returns
Array
(
[0] => Array
(
[u] => Array
(
[id] => 2
[firstname] => MyFirstName
)
[c] => Array
(
[zip] => 12345
)
)
)
When we try to do the same thing with CakePHP 3.8
$connection = ConnectionManager::get('default');
$sql = "SELECT u.id, u.firstname FROM users u, contacts c WHERE u.id = 1 and c.id = u.contact_id";
$u = $connection->execute($sql)->fetchAll();
$this->log($u);
the result is
Array
(
[0] => Array
(
[0] => 2
[1] => MyFirstName
[2] => 12345
)
)
In order to migrate safely: Is there a way to make CakePHP 3.8 / 4 return query results using the same array structure as in CakePHP 2?
Rewriting the statements to use ORM is not an option. The example above is not a real code - the real queries are more complex.

Per the documentation, you should be using ->fetchAll('assoc'). The output of this isn't quite identical to what you had in Cake 2, but at least the field names are present.

Related

Cakephp condition on belongs to many table

I have 3 tables
users
bases
bases_users
In usersTable I have created a belongsToMany relation like
$this->belongsToMany('Bases', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'base_id',
'joinTable' => 'bases_users',
]);
Now I am trying to fetch all users where user is under this login user bases.
Query like below
SELECT users.name FROM users
INNER JOIN bases_users on bases_users.user_id = users.id
WHERE bases_users.base_id in (1,2) //login user 1 has tow bases (1,2)
GROUP BY Users.id
I'm trying to get accepted data using below query with content
$this->Users->find()
->contain([
'UserAuthorities',
'Bases' => function($q) use($userId){
return $q->where(['Bases.users.id' => $userId]);
}
])
How can I match the data where BasesUsers.user_id = $userId ?
I have tried the code after #ndm comment like below
$queryUsers = $this->Users->find()
->contain([
'UserAuthorities',
'Bases'
])
->innerJoinWith('BasesUsers', function(\Cake\ORM\Query $q) {
return $q->where(['BasesUsers.user_id' => 10]);
})
->where($conditions)
->order($order)
;
Query is generating :
SELECT
...
FROM
users Users
INNER JOIN bases_users BasesUsers ON (
BasesUsers.user_id = 10
AND Users.id = BasesUsers.user_id
)
INNER JOIN user_authorities UserAuthorities ON UserAuthorities.id = Users.user_authority_id
WHERE
(
Users.company_id = 1
AND Users.is_deleted = FALSE
)
ORDER BY
Users.created DESC
LIMIT
20 OFFSET 0
Here My expecting query is
INNER JOIN bases_users BasesUsers ON (
BasesUsers.user_id = 10
)
Sample data
Users table :
id, name,
1 A
2 B
3 C
4 D
Bases table :
id Name
1 Base 1
2 Base 2
3 Base 3
BasesUsers:
id user_id base_id
1 1 1
2 1 2
3 2 1
4 3 1
5 4 3
6 3 2
Expected result for user id 1 logged in
name bases
A Base1,Base2
B Base1
C Base1,Base2
How I will remove AND Users.id = BasesUsers.user_id from innerJoin with ?
I have the same problem, not exactly for compare two id's but for check if a column match a condition, i solve this using matching()
Read this of CakePHP documentation:
https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html#filtering-by-associated-data-via-matching-and-joins
I use those examples and work perfect. Hope this can help you.

UpdateAll is not working with bigint() fields

I'm quite new to CakePHP and have an issue. I'm trying to update one field in a row with this:
$options = array('conditions' => array('User.id' => $_POST['userid']));
$user = $this->User->find('first', $options);
$currentpoint = $user['User']['point'];
$correctanswerpoint = Configure::read('CORRECT_SYSTEMANSWER_POINT');
$newpoint = $currentpoint + $correctanswerpoint;
$this->User->updateAll(array('User.point'=>$newpoint), array('User.id'=>$_POST['userid']));
There are no errors, but the field point (bigint(20)) is not being updated. I've changed the field to update to another one in the same row which is of smallint type and the update goes through fine.
My debugging log seems to show that the SQL query is ok too:
[query] => UPDATE `askyoode_askyoo`.`users` AS `User` LEFT JOIN `askyoode_askyoo`.`countries` AS `Country` ON (`User`.`country_id` = `Country`.`id`) LEFT JOIN `askyoode_askyoo`.`accesstypes` AS `Accesstype` ON (`User`.`accesstype_id` = `Accesstype`.`id`) SET `User`.`point` = 57 WHERE `User`.`id` = 124
[params] => Array
(
)
[affected] => 1
[numRows] => 1
[took] => 12
)
And manually running the same SQL statement in MySQL works. Been tearing out my hair for the whole day trying to resolve this. Any help is greatly appreciated. Thanks!!

Subquerys in cakephp 3.x, new ORM?

I'm new in Cakephp 3.x and I'm having some trouble to create a subquery in the new ORM format. I have this report in my application, that needs to return the follow result:
1. There are three entities - Users, Calls, CallStatus.
2. Users hasMany Calls, Calls hasMany CallStatus.
3. I need to count how many CallStatus each user has in Calls.
Now follow the query that I need to put on new ORM format:
SELECT U.name,
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =1 and C.user_id=U.id) AS 'Unavailable',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =2 and C.user_id=U.id) AS 'Busy',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =3 and C.user_id=U.id) AS 'Contacted',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =4 and C.user_id=U.id) AS 'Error'
FROM `users` AS U
WHERE U.profile=3 and U.is_active=1
Could someone give me a help, please? Thanks
If I understand you correctly, you want to see the number of calls for every callstatus you have for a specific user.
Try the following. Note that I used the CakePHP convention for naming the callstatuses (which is plural).
// get the tableregistry
use Cake\ORM\TableRegistry;
$callstatuses = Cake\ORM\TableRegistry::get('Callstatuses');
// for user with id 2, get the number of calls for each callstatus
$callstatuses->find()
->contain(['Calls'])
->where(['Calls.user_id' => 2, 'User.is_active' => 1])
->countBy('name')
->toArray();
// output could be:
//[ 'Unavailable' => 2, 'Busy' => 1 ]
You can find information about creating queries in the CakePHP book: see 'Query Builder'.
If you want to know more about working with/on queries, note that queries are Collections. Anything you can do on a Collection object, you can also do in a Query object. See the Collection section in the CakePHP book.
You have to use subqueries, as many as you want!
Here is an example for your case:
$q = $this->Calls->find();
$q1->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 1]);
$q2->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 2]);
$q3->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 3]);
$q4->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 4]);
$qUsers = $this->Users->find()
->select([
'id',
'first_name',
'Unavailable' => $q1,
'Busy' => $q2,
'Contacted' => $q3,
'Error' => $q4
])
->where(['profile' => 3, 'active' => 1])
->all();
Note: That nicer if you use a loop to create suqueries in this case.

CakePHP count query gives different result when run in phpmyadmin

I am running the following query on my database:-
SELECT COUNT(*) AS COUNT, `Doctor`.`device_type` FROM `doctors` AS `Doctor` WHERE 1 = 1 GROUP BY `Doctor`.`device_type`
and it gives the result:-
count device_type
47 Android
23 iPhone
Whereas when running this query as a CakePHP query it gives the result as '2':-
$this->Doctor->find('count',array('group'=>'Doctor.device_type'));
Can anyone please suggest why this is happening?
The CakePHP result is correct as it is returning a count of the number of results returned by your query. In your case you have 2 rows: 'Android' and 'iPhone'.
find('count') always returns an integer, not an array. What Cake is doing is basically this:-
$data = $this->Doctor->find('all', array('group' => 'Doctor.device_type'));
$count = count($data); // This is what find('count') will return.
You need to do something like the following instead:-
$data = $this->Doctor->find('all', array(
'fields' => array(
'Doctor.device_type',
'COUNT(Doctor.*) AS count'
)
'group' => 'Doctor.device_type'
));

Covert into Cakephp query with subquery

Does anybody know how transform this query:
SELECT * from diminventory where partnumber='350964-B22' or partnumber in (SELECT partnumber from dimparts where parentpartnumber='350964-b22')
in a cakephp query
Thanks
I'm not 100% certain what you're asking for as yet, but here's a brief tutorial on cakephp querying.
$this->ModelName->query("SELECT * FROM tablename LIMIT 2;");
You query from the model, but you use the literal tablename. You use the SQL "AS" keyword to rename the keys of the resultant array.
Sample results:
Array
(
[0] => Array
(
[tablename] => Array
(
[id] => 1304
[user_id] => 759
)
)
[1] => Array
(
[tablename] => Array
(
[id] => 1305
[user_id] => 759
)
)
)
He is asking for Cakephp query not custom SQL query. In the controller:
$partnumber = $this->diminventory->find('all',array('conditions' => array('diminventory.partnumber' => '350964-B22')));
$this->set('partnumber',$partnumber);
set function passes a variable as $partnumber to the view. Then int view (which is .ctp file) you need to output the array.
foreach($partnumber as $partnumbers){
echo $partnumbers;
}
Make sure that diminventory table has 's' after the name otherwise it won't work as this is part of Cakephp's strict naming conventions.
Tutorial for find function:
http://book.cakephp.org/view/1018/find
To do it fully Cake'ish, you need to use CakePHP subquery:
$dbo = $this->User->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('`Dimpart`.`partnumber`'),
'table' => $dbo->fullTableName($this->Dimpart),
'alias' => 'Dimpart',
'conditions' => array('`Dimpart`.`parentpartnumber`' => '350964-b22'),
),
$this->Dimpart
);
$subQuery = ' `DiminventoryEntry`.`partnumber` IN (' . $subQuery . ') ';
$subQueryExpression = $dbo->expression($subQuery);
$conditions[] = $subQueryExpression;
$conditions['DiminventoryEntry.partnumber'] = '350964-B22';
$result = $this->DiminventoryEntry->find('all', compact('conditions'));
Where Dimpart is your model for table dimparts, and DiminventoryEntry is your model for table diminventory (which is not actually Cake'ish, you should've renamed your table according to conventions).

Resources