I have a few questions about Join in Cakephp - cakephp

Thank you for reading it.
I'm studying cakephp and have a few questions about Join in Cakephp.
I coudn't find any appropriate answer on internet
1, What is difference between innerjoin and innerjoinwith (like leftjoin and leftjoinwith) I thought It is related with performence issue, but couldn't find any clue.
2, I can't get the exact difference between "matching" and "contain"
I know the way they retrieving data is different, but I thought the result looks same as the result of matching and matching is using innerjoin, and contain is using leftjoin.
but but I can't find what is the difference between the code below(using contain) and matching.
$query = $articles->find()->contain([
'Comments' => function ($q) {
return $q
->select(['body', 'author_id'])
->where(['Comments.approved' => true]);
}
]);
3, what is "_matchingData" in matching?
I read this description, that is
"The innerJoinWith() method works the same as matching(), that means that you can use dot notation to join deeply nested associations: Again, the only difference is that no additional columns will be added to the result set, and no _matchingData property will be set."
but I couldn't find any description about _matchingData. even in API... what it is?
you can response one of them, it is okay. please help me out

1, What is difference between innerjoin and innerjoinwith (like leftjoin and leftjoinwith) I thought It is related with performence issue, but couldn't find any clue.
=>
(1) here 'innerJoinWith' is a callable which will apply right/inner join
(2) for left join 'leftJoinWith' is the callable
https://book.cakephp.org/3.0/en/orm/query-builder.html#using-innerjoinwith
https://book.cakephp.org/3.0/en/orm/query-builder.html#using-leftjoinwith
2, I can't get the exact difference between "matching" and "contain"
I know the way they retrieving data is different, but I thought the result looks same as the result of enter code here matching and matching is using innerjoin, and contain is using leftjoin.
but but I can't find what is the difference between the code below(using contain) and matching.
$query = $articles->find()->contain([
'Comments' => function ($q) {
return $q
->select(['body', 'author_id'])
->where(['Comments.approved' => true]);
}
]);
=> In short contain applies leftJoin and matching apply right/inner join to the query.
please read this first line on this link https://book.cakephp.org/3.0/en/orm/query-builder.html#using-innerjoinwith
3, what is "_matchingData" in matching?
I read this description, that is
"The innerJoinWith() method works the same as matching(), that means that you can use dot notation to join deeply nested associations: Again, the only difference is that no additional columns will be added to the result set, and no _matchingData property will be set."
but I couldn't find any description about _matchingData. even in API... what it is?
=> _matchingData() is an entity property created when matching() is used. It contains data which is selected matching
$query = $articles->find();
$query = $query->matching('Comments', function($q){
$q->select(['Comments.body', 'Comments.author_id']);
$q->where(['Comments.is_approved' => 1]);
return $q;
});
Above query will have _matchingData property contained with Comments.body, Comments.author_id

Related

Using SQL Functions in cakephp returns error

I am following Using SQL Functions to build my query. I tend to get the first due instalment. My query was working (on cakephp3.1) before updating the cakephp to version 3.3 (by composer).
In my Controller
$this->loadModel('Orders');
$order = $this->Orders->find()
->where(['order_id' => $orderId])
->contain([
'PaymentInstalments' => function($q) {
return $q->find('firstDue');
},
'Users' => function($q) {
return $q->select(['email']);
}
])
->first();
On my paymentInstalments Table
public function findFirstDue(Query $query, array $options)
{
$alias = $this->alias();
$query
->select([
// "id" => $query->func()->min("$alias.id"), ## This use to work before but now is not working
'id' => $query->func()->min(function($row){
return $row->id;
}),
'order_id', 'amount', 'date_due', 'transaction_id'
])
->contain([
'Transactions' => function($q) {
return $q
->select([
'id', 'transaction_date'
])
->where(function ($exp, $q) {
return $exp->isNull('transaction_date');
});
}
]);
debug($query->__debugInfo()['sql']);
die;
return $query;
}
Here is print of my query.
'SELECT PaymentInstalments.id AS `PaymentInstalments__id`, PaymentInstalments.order_id AS `PaymentInstalments__order_id`, PaymentInstalments.instalment_num AS `PaymentInstalments__instalment_num`, PaymentInstalments.amount AS `PaymentInstalments__amount`, PaymentInstalments.date_due AS `PaymentInstalments__date_due`, PaymentInstalments.payment_email_sent AS `PaymentInstalments__payment_email_sent`, PaymentInstalments.transaction_id AS `PaymentInstalments__transaction_id`, {
"id": 41408,
"order_id": "10000",
"instalment_num": 1,
"amount": 100,
"date_due": "2016-08-25T12:15:00+01:00",
"payment_email_sent": false,
"transaction_id": null
} AS `PaymentInstalments`.`id`, Transactions.id AS `Transactions__id`, Transactions.transaction_date AS `Transactions__transaction_date` FROM payment_instalments PaymentInstalments LEFT JOIN transactions Transactions ON ((Transactions.transaction_date) IS NULL AND Transactions.id = (PaymentInstalments.transaction_id)) WHERE PaymentInstalments.order_id in (:c0)'
The problem is if I use "id" => $query->func()->min("$alias.id"), I get this error:
You are required to select the "PaymentInstalments.order_id" field(s)
And if I use this
'id' => $query->func()->min(function($row){
return $row->id;
}),`
I get this error:
Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You
have an error in your SQL syntax; check the manual that corresponds to
your MySQL server version for the right syntax to use near '"id":
41408, "order_id": "10000", "instalment_num": 1, "amount": ' at
line 2
Any help please
You cannot use collection methods
$query->min() is a collection method, respectively will execute the query and call the method on the resulting result set, ie it has nothing to do with SQL functions. Just look at your debug output, there's result set data in your SQL query.
See Cookbook > Database Access & ORM > Query Builder > Queries Are Collection Objects
$query->func()->min() is the way to go, that's what generates an SQL function expression object.
There's a bug in the core regarding function expressions
That being said, what you are experiencing is a bug that is present in the foreign key presence existence check, and is being triggered by having expressions in the select list. You should see even more errors, respectively warnings like
Object of class Cake\Database\Expression\FunctionExpression could not be converted to string
Please make sure to always include stuff like that in your questions! If you don't see it, try cranking up your error reporting level.
The cause of that warning, combined with a PHP bug, is the source of the whole problem, the select list ist being passed to array_diff(), which uses string comparison, ie (string)$elementA === (string)$elementB, which will fail since the expression object cannot be converted to a string.
PHP is weird
And now comes an interesting quirk, without that you'd only see a warning, but the query would run fine.
Before PHP 7, if you put the key that is being searched for, ie order_id, directly after the function expression in the select list, then array_diff() won't find it, and say that it is missing. However if you have at least one additional element between it and the function expression, ie something like
'id' => $query->func()->min("$alias.id"),
// instead of here
'amount',
'order_id', // put it here
'date_due',
'transaction_id'
then array_diff() will find it, and the query will execute fine despite the string conversion error being thrown. But that's not all, no no, it wouldn't be PHP if there weren't really weird things going on.
The whole "place the key differently and behavior changes" only happens when array functions like asort() are being invoked inside of the error handler callback. It doesn't matter on what data they are being invoked, it doesn't need to be connected to the error in any way, it could just be something like $foo = []; asort($foo);, that would already cause that weird behavior.
https://3v4l.org/2nT5M
You gotta love PHP :)
Use a hardcoded SQL fragment as a workaround
As a workaround until the bug is fixed, you could pass an SQL fragment as a string instead, like
'id' => 'MIN(PaymentInstalments.id)'
Last but not least
Please report the string conversion problem as a bug over at GitHub.
You could temporarily get it to work by hard-coding it:
$alias = $this->alias();
$query->select(['id' => "MIN($alias.id)"]);

CakePHP3 associated entities not coming through from find() (or get) even with contain clause

[edit] solved, see: https://stackoverflow.com/a/32638610/221650
Some associated data is not coming through, although it's in the contain clause.
I can get $project->Participants if I set a belongsToMany relationship in the Projects table, relating to Participants through ProjectParticipants, but that way I still can't reach other tables associated with ProjectParticipants even with $project->participants->_joinData->other_related_table
How would I do to get ProjectParticipants and Participants with the same query?
Code:
// Database: Projects <--1:N-- ProjectParticipants --M:1--> Participants
// ProjectController:
$project = $this->Projects->get($id, ['contain'=>[
'ProjectParticipants.Participants']);
// ProjectsTable:
$this->hasMany('ProjectParticipants', [
'foreignKey' => 'project_id']);
// ProjectParticipantsTable:
$this->belongsTo('Participants', [
'foreignKey' => 'participant_id']);
It's a very complicated many to many relationship.
//projectTable
$this->belongsToMany('Participants');
//praticipantsTable
$this->belongsToMany('Projects');
It's well explained in the Bookmark Tutorial.
Sorry, it's working fine, I hadn't noticed there was a custom Entity\Project::_getParticipants() that was returning a Collection.
A simple debug on that object showed everything alright, but then when running it through a foreach, the associations disappeared.

Can't use paginate on a query result

According to the doc http://book.cakephp.org/3.0/en/controllers/components/pagination.html, I'd like to paginate a result of a query like below:
$unlocked_sitesQuery = $this->Deviceconnections
->find()
->contain([
'Agthemes.Sites',
'Agthemes.Agpois',
'Agthemes.Agthemelanguages'
])
->where(['request' => 'unlock'])
->groupBy('agtheme.site.id');
$unlocked_sites = $this->paginate($unlocked_sitesQuery);
But I get the following error:
Error: Call to undefined method ArrayIterator::alias()
File /home/mywebsite/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php
Line: 154
What does it mean?
EDIT
It seems that #ndm is right but the doc says:
By default the paginate() method will use the default model for a controller. You can also pass the resulting query of a find method:
public function index()
{
$query = $this->Articles->find('popular')->where(['author_id' => 1]);
$this->set('articles', $this->paginate($query));
}
So it should work on a result set. Or I didn't understand what the doc explains. Possible.
It means that you are passing the wrong type of object. Pagination on result sets is not supported, only tables (either objects or names) and queries are.
groupBy is not a method of the query class, it's one of the magic methods that are causing the query to be executed, and forward the method call to the resulting result set. So you end up calling Cake\ORM\ResultSet::groupBy(), which returns another collection.
Cookbook > ... ORM > Query Builder > Queries Are Collection Objects
Cookbook > ... ORM > Retrieving Data & Results Sets > Working with Result Sets
So if you need such grouped results in pagination, then you have to solve this (at least partially) on SQL level, for example by fetching the results the other way around, ie fetch Sites and their associations, and filter by Deviceconnections.request, something like this (no guarantee that this will give you the desired result, but the example should give you a hint!):
$query = $Sites
->find()
->contain([
'Agthemes.Deviceconnections',
'Agthemes.Agpois',
'Agthemes.Agthemelanguages'
])
->matching('Agthemes.Deviceconnections', function(\Cake\ORM\Query $query) {
return $query
->where([
'Deviceconnections.request' => 'unlock'
]);
});
You'd of course have to adapt your view code accordingly.

Cakephp 3.0: Multiple matching conditions

I'm trying to daisy-chain matching() calls on a query, or find an equivalent way to achieve the same result. Specifically, here's what I'd like to do:
$matchedItemVersion = $this->ItemVersions->find()
->where(['version' => $verion])
->matching('Items', function($q) use ($ipnCore) {
return $q->where(['ipn_core' => $ipnCore]);
})
->matching('Items.ItemTypes.ItemSuperGroups', function($q) use ($itemSuperGroupName) {
return $q->where(['name' => $itemSuperGroupName]);
})
->first()
->toArray();
This would let me filter my ItemVersions results by two conditions on associated models. However, right now, the second matching() call basically overrides the first one, so the first matching() call has no effect on the query.
We know that you cannot chain a matching() and a contain()(https://github.com/cakephp/cakephp/issues/5109), but is there a way to do it with matching(), or a way to get the same result?
Appreciate the help.

How do I build this complex database query in CakePHP 3.0?

This is my database design, or at least the tables that are relevant to this question.
I want to build a query that returns a single page (find will be based on the path attribute), with the associated container, with its associated child-containers in threaded form and all of those containers should individually have their associated blocks with them (preferably in the right order sorted by the index column from the blocks_pages table).
Can anybody give me a clue how to wrap that all up with the query-builder? Or if that is not possible, then is it possible to do it using the new map/reduce feature, since the after-find function has been removed?
In case it helps, this will be the visualized result, if you just ignore the magenta Article-box for a moment.
Try this
$pagesTable
->find()
->where(['path' => $myPath])
->contain([
'Containers.ChildContainers' => function($q) {
return $q->formatResults(function($results) {
return $results->map(function($container) {
$container->nested = $container->source()
->find('children', ['for' => $container->id])
->find('threaded')
->contain(['Blocks']);
return $container;
});
});
},
'Containers.ChildContainers.Blocks'
])

Resources