Cakephp 3 dynamically build contain - cakephp

How can I dynamically build the contain in the new cakephp 3 query builder. This is what I have now:
$query = $dbTable->find()
->select($contain['select']['fields'])
->contain(function($q) use($array,$contain){
$new = [];
foreach($array as $v){
if(isset($contain['contains'][$v])){
$fields = $contain['contains'][$v];
$new[$v] = $q->select($fields);
}
}
return $new;
});
But I am getting several errors with this:
Warning (2): Illegal offset type in isset or empty [CORE\src\ORM\EagerLoader.php, line 198]
Warning (2): strpos() expects parameter 1 to be string, object given [CORE\src\ORM\EagerLoader.php, line 203]
Warning (2): Illegal offset type [CORE\src\ORM\EagerLoader.php, line 223]
Warning (2): Illegal offset type [CORE\src\ORM\EagerLoader.php, line 224]

As already mentioned by Lorenzo, that's not how it works, contain() doesn't accept callables, just look at the docs:
http://api.cakephp.org/3.0/class-Cake.ORM.Query.html#_contain
Also your code would invoke select() multiple times on one and the same query, that wouldn't work anyways.
However, looking at your code it seems that you could simply make use of the fields option, ie build a simple array to pass to contain(). This is shown in the docs, the example however will trigger an error as it seems to be necessary to explicitly set the foreign key field too:
$query->contain([
'Articles' => [
'fields' => ['foreign_key_column_name', 'title']
]
]);

You cannot use contain with a closure inside. If you believe this is a good idea (I think it could be) then open a enhancement request on github.

To get all fields in the table Articles do this in your controller.
$query = $this->modelName->find('all')
->contain('Articles');
If you want a expecify field use this:
$query = $this->modelName->find('list', [
'keyField' => 'id',
'valueField' => 'author.name'
])->contain(['Articles']);
To more informations about: https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html

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)"]);

CakePHP 3: Setting options for 'contain' when paginating

What's the correct way to limit the number of contained associated records when paginating?
The docs don't appear to address how to set options for 'contain' while paginating, but $paginate['contain']['AssociatedModel']['limit'] = 1; seemed to make sense. However, that line is resulting the following error for me in CakePHP 3.1.3:
Fatal error: Unsupported operand types in ...\vendor\cakephp\cakephp\src\ORM\EagerLoader.php on line 312
The error being generated because, in the line $pointer[$table] = $options + $pointer[$table];, $options is 1 and $pointer[$table] is an array.
Confusingly, setting $paginate['contain']['AssociatedModel']['fields'] works as expected, but setting 'limit' or 'order' results in that same error.
Despite the fact that setting $paginate['contain']['AssociatedModel']['fields'] = [...] works, other options need to be set using functions. The following code fixes my problem:
$paginate['contain']['AssociatedModel'] = function($q) {
return $q
->select([...])
->limit(1)
->order([...]);
};

CakePHP - how to handle double quote in conditions array

See the following example code:
$conditions = array("Post.title" => 'This is a "Book"');
// Example usage with a model:
$this->Post->find('first', array('conditions' => $conditions));
Because find() actually looks for title = 'This is a \"Book\"', no result returned. I am wondering how to prevent find() from adding backslashes. Or is there any other solution?
==fixed==
*Actually the error occurred when I used updateAll($field, $conditions), not find(). I did not put the quote around literal values. For example, $field = array('title' => $some_title) should be $field = array('title' => "'" . Sanitize::escape($some_title) . "'") . Don't like the way CakePHP handles this though.*
You must be mistaken. The error must be somewhere else.
The resulting SQL query does contain
LIKE 'foo \"bar\"'
But that escaping is actually intentional.
I will still turn up the DB entry with foo "bar" - I just tried it myself with cake2.3/2.4.
So CakePHP is working correctly.
Just checked with cakePhp version 2.3.5, The double quotes are working fine, Please check below code for a Profile controller.
$data = $this->Profile->find('all',array('conditions'=>array('Profile.type'=>'user "one"')));
pr($data);

cakephp Paginator -> sort - model option

I am having some issues sorting the table data by a field of a different model.
From here: http://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html
It says I can add in the 'model' option but when I try:
echo $this->Paginator->sort('unit', 'Unit', array('model' => 'Unit'));
I get this error:
Warning (2): array_filter() expects parameter 1 to be array, null given [CORE/Cake/View/Helper/PaginatorHelper.php, line 395]
Warning (2): array_merge() [function.array-merge]: Argument #1 is not an array [CORE/Cake/View/Helper/PaginatorHelper.php, line 395]
Any idea what is going on here? The Main / default model is Card and I need to order by the Unit model for one of the column headings.
Thanks
If you are showing records in a list out of some tables, then you can use it via:
<?php echo $this->Paginator->sort('Unit.unit', 'Unit');
It will perfectly work without passing third argument model option.
Just a reminder for newer Versions: Associated models are not automatically loaded in CakePHP3s Paginator. Make sure you include the 'sortWhitelist' Option, see https://book.cakephp.org/3.0/en/controllers/components/pagination.html#control-which-fields-used-for-ordering
Please try below code
echo $this->Paginator->sort('Unit.unit', 'Unit', array('model' => 'Unit'));
Let me know if any.

save() returning false, but with no error in CakePHP

My debug value is set to 2, and it's displaying all the queries, except the one I need.
I have an Items controller method that is calling this method in the User model (Item belongsTo User):
function add_basic($email, $password) {
$this->create();
$this->set(array(
'email' => $email,
'password' => $password
));
if($this->save()) {
return $this->id;
}
else {
return false;
}
}
I have confirmed that $email and $password are being passed into the function correctly (and are populated with legit data). email and password are the names of the fields in the User model.
I have also confirmed that on $this->save() it is returning false, but when I view the page where this occurs, the query is not being printed in the debug, and there is no error being thrown, so I have no idea whats going wrong.
Any ideas on how I can see the error, or why the query doesn't seem to be getting executed?
It's weird, cause right after this, I have another model saving data to it in the exact same fashion, it goes off without a hitch.
This will probably give you the info you need (assuming it's not saving because of invalid data, of course):
if(!$this->save()){
debug($this->validationErrors); die();
}
Have you got a beforeValidate() or beforeSave() method in the model or app model? Ifso, are they returning true? Failing that, use a debugger, set a break point in your IDE at the top of cake/libs/models/model.php save() method and step through the code until it returns false. Failing that add die('here'); calls.
Try this:
if ($this->save()) {
return $this->id;
}
else {
var_dump($this->invalidFields());
return false;
}
#cakePHP 3.6 and above: By default, the request data will be validated before it is converted into entities. If any validation rules fail, the returned entity will contain errors. It can be read by getErrors() method.
The fields with errors will not be present in the returned entity:
Say, you have an entity
use App\Model\Entity\Article;
$entity = $this->ModelName->newEntity([
'id' => 1,
'title' => 'New Article',
'created' => new DateTime('now')
]);
$result = $this->ModelName->save($entity);
\Cake\Log\Log::debug($entity->getErrors());
If you’d like to disable validation when converting request data, set the validate option to false:
$article = $articles->newEntity(
$this->request->getData(),
['validate' => false]
);
Ref: https://book.cakephp.org/3/en/orm/validation.html
Make sure to check your tables:
Does ID have auto increment enabled?
Is id your primary key?
the auto_increment issues killed me.
Easy way to check: if any of your rows have ID = 0, auto_increment is likely disabled.
CakePHP 3.6
$entity = $this->Model->newEntity([
'account_id' => $id,
'gallery_id' => $gallery_id
]);
$result = $this->Model->save($entity);
print_r($entity->getErrors());
The other situation where CakePHP fails to report any $this->Model->validationErrors and no other errors is potentially when $this->request->data isn't as Cake expects and is simply ignoring your data, not saving, no validation errors. For example if your data was provided by DataTables you might see this format $this->request->data[0]['Model']['some_field'].
$this->Model->save($this->request->data[0]) will work however.

Resources