Cakephp 3 - get fields and conditions in beforefind - cakephp

In cakephp 2.x in beforeFind I could check if some condition is set by
!empty($queryData['conditions'][$this->alias.'.field']) or get the list of fields that would be retrived simply by $queryData['fields']. How to achieve this in cakephp 3.x ?
In beforeFind
public function beforeFind(Event $event, Query $query, $options, $primary)
{
}
the options is empty. The $query I can use to add conditions by $query->where(...), but how to check what fields are set to be retrieved or what conditions are already applied ?
Thanks

Taken from the CakePHP 3.0 API documentation:
traverse( callable $visitor , array $parts [] )
Will iterate over every specified part. Traversing functions can aggregate results using variables in the closure or instance variables. This function is commonly used as a way for traversing all query parts that are going to be used for constructing a query.
The callback will receive 2 parameters, the first one is the value of the query part that is being iterated and the second the name of such part.
Example:
$query->select(['title'])->from('articles')->traverse(function ($value, $clause) {
if ($clause === 'select') {
var_dump($value);
}
}, ['select', 'from']);
So just call $query->traverse() and provide the callback closure and do your checks inside of it. See also traverseExpressions().

Thanks for the hint, but method "traverse" in the 4th version I already did not work like this.
I looked at it in the Cake core:
public function beforeFind($event, $query, $options, $primary)
{
$query
->clause('where')
->iterateParts(function ($callable) use (&$params): void {
if ($callable->getField() === 'url') {
// do something
}
$params[] = [
$callable->getField() . ' ' . $callable->getOperator() => $callable->getValue()
];
});
$query->where($params);
}

Related

Sorting on nested relationship

I have the following relationship:
Rounds hasMany Results
Results belongsTo Drivers
Results are shown on Rounds's view page: rounds/view/{id}. I want to sort Results based on Drivers.name.
This is my RoundsController::view method:
public function view($id = null)
{
$this->paginate = [
'sortableFields' => [
'Results.Drivers.name',
]
];
$round = $this->Rounds->get($id, [
'contain' => ['Championships' => ['Teams'], 'Tracks', 'Results' => ['Drivers', 'Constructors']],
]);
$this->set(compact('round'));
}
And in my rounds/view.ctp file, I have this:
<?= $this->Paginator->sort('Results.Drivers.name', __('Driver')); ?>
However, no matter what I change, it doesn't sort by driver name. Also, when I click it multiple times, the direction stays asc.
Why is it not working?
Like #ndm mentioned, your code is showing only one specific Round. View method/function is receiving an $id arg, this $id is related to the round id you want to show in view page.
You can not order this (you can do it but it doesnt make sense) because you are receiving only one Round, and it doesnt make sense to order only one row.
This view method is usually used with this goal: show individual/personal information about an Entity (Round entity in your case). Please, note I use the word USUALLY.
I think you could use the index method for this purpose (this method/function is usually used to list entities from a model and its association data). You could create a new method/function for your porpuse as well if you already are using index method for another goal.
I suggest something like this, assuming you will use the index function:
public function index()
{
$rounds = $this->Rounds->find('all')
->contain([
'Championships' => ['Teams'],
'Tracks',
'Results' => [
'Constructors'
[
'Drivers' => function (Query $q) {
return $q->order('Drivers.name');;
}
]
]
]);
$rounds= $this->paginate($rounds);
$this->set(compact('rounds'));
}
You could pass conditions into the contain or do the same into paginate method. I always use the first (just preferences).
NOTE: this code may not work, it is an idea of what you could do.
Finders documentation: https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html#using-finders-to-load-data
Passing conditions to contains: https://book.cakephp.org/4/en/orm/query-builder.html#passing-conditions-to-contain

CakePHP 3.x beforeFind() callback, when it fire?

I use CakePHP beforeFind() callback for my Tables ain this way:
public function beforeFind($event, $query, $options, $primary)
{
// if ->applyOptions(['default' => false]) not use default conditions
if(isset($options['default']) && $options['default'] == false){
return $query;
}
$query->where(['visible' => 1]);
$query->order(['sort' => 'ASC']);
return $query;
}
And this is correct, BUT WHEN i try to retrieve my result sometimes it fired and sometimes no.
NO - I think that if i use table registry for get my table: TableRegistry::get('MyTable')->find()->first() its not fired.
YES - And if i use, from same controller, $this->Pages->find()->first() its fired!
Its work in this way only for me? Or for have the beforeFind() fired i need to do some additional stuff?
You need to make sure your fields are not ambiguous as part of your query. Try adding the name of the model in the where queries, otherwise you'll get PDOExceptions:
$query->where(['myModel.visible'=>1]);

Cakephp Paginate Find

I want to list the posts of a given user. It work but paginate is not accurate.
My code is the following
public function index($userid = null) {
if ($this->Post->exists($userid)) {
$this->set('posts',$this->Post->find('all',array('conditions'=>array('user_id'=>$userid))),
$this->paginate());
} else
{
$this->Post->recursive = 0;
$this->set('posts', $this->paginate());
}
The result give the correct list --> 3 posts, but the paginator display page number 1 and 2
Can you help me?
Thank you
Refer to the documentation
The code in the question is quite confused.
find
The find method only has two parameters:
find(string $type = 'first', array $params = array())
The third parameter (the result of calling paginate) isn't used and will be ignored - but it will setup the view variables for the pagination helper, based on the conditions used in the paginate call - there are no conditions being used.
It is not possible to paginate the result of a find call - to do so restructure the code to call paginate instead of find.
paginate
The paginate method is just a proxy for the paginator component - it can be used in several ways, this one (controller code example):
$this->paginate($conditions)
Is the most appropriate usage for the case in the question i.e. the complete action code should be similar to:
public function index($userId = null) {
$conditions = array();
if ($userId) {
$conditions['Post.user_id'] = $userId;
}
$this->set('posts',$this->paginate($conditions));
}
Note that logically, if a user id is requested that doesn't exist the response should be nothing - not everything.
I'm quite sure that conditions for paginate do now work that way.
If you want to set conditions for paginations you should do it as follows:
$this->paginate = array('conditions' => array('Post.user_id' => $userid)));
$this->set('posts', $this->paginate());
And yes, the result stored in $posts ( in view ) will be proper as you assigned proper find result to it, meanwhile you've paginated post model without any conditions whatsoever.
First off, you're checking to see if the post exists but using the $userid. Are you trying to see "if the user exists, get the posts for that user, or else get posts for ALL users"? As you have it right now, say you have the $userid = 159, but the max Post.id in your database is 28, then the condition is not being met because it is checking to see whether or not there is a Post with the id = 159 that exists, which it doesn't.
Second, your conditions are wrong. You are performing a find and then a paginate which are two separate queries. The conditions are being implemented on the find query but not the paginate but you are only displaying the find results.
public function index($userid = null) {
// setting recursive outside of if statement makes it applicable either way
$this->Post->recursive = 0;
// check if user exists
if ($this->Post->User->exists($userid)) {
// get posts for user
$this->set('posts', $this->paginate('Post', array('Post.user_id' => $userid));
}
else{
// get all posts
$this->set('posts', $this->paginate('Post'));
}
} // end index function

CakePHP: afterFind is weird with associations

So afterFind works fine and dandy when I'm within the corresponding model/controller. However, when calling an associated model, the data sent to the afterFind callback is formatted differently. This causes afterFind to crap out because it can't find the same array indexes it did when just working within the original model/controller.
Anyone know why, or what a fix might be?
$primary may not be very helpful; I've found that it is always false when using ContainableBehaviour beyond the first depth:
$this->Model->find('first', array(
'contain' => array(
'SecondaryModel' => array(
'TertiaryModel',
),
),
));
If you're setting a value based on a related model, you can check for its presence to deal with either structure like this:
function afterFind($results, $primary) {
if (isset($results['TertiaryModel'])) {
$results['secondary_model_field'] = 'value';
}
else {
foreach ($results as &$result) {
if (is_array($result) && isset($result['TertiaryModel'])) {
$result[$this->alias]['secondary_model_field'] = 'value';
}
} unset($result);
}
return $results;
}
Alternately you may be able to just check for the location of a field on the model itself. If the field doesn't exist at the top level, you will need to iterate over the set of results.
This is what the second parameter to afterFind callback is for.
$primary tells if you if the find was called from this model directly (true), or if it was called by an associated model (false).
A note from the book:
The $primary parameter indicates whether or not the current model was
the model that the query originated on or whether or not this model
was queried as an association. If a model is queried as an
association the format of $results can differ;
Code expecting $primary to be true will probably get a "Cannot use
string offset as an array" fatal error from PHP if a recursive find
is used.
So you may need different processing logic depending on the value of $primary
It appears that Cake 2.6 includes a fix for this, ensuring that all $results arrays are consistently formatted. I've done a little testing with the RC release and it does seem to work, with arrays all being passed in the format {n}.ModelName.data.
The fix is enabled by default, but you can also revert to the legacy behaviour if need be. Just add the following to your model (or AppModel) definition:
public $useConsistentAfterFind = false;

CakePHP: Can I ignore a field when reading the Model from the DB?

In one of my models, I have a "LONGTEXT" field that has a big dump of a bunch of stuff that I never care to read, and it slows things down, since I'm moving much more data between the DB and the web app.
Is there a way to specify in the model that I want CakePHP to simply ignore that field, and never read it or do anything with it?
I really want to avoid the hassle of creating a separate table and a separate model, only for this field.
Thanks!
Daniel
As #SpawnCxy said, you'll need to use the 'fields' => array(...) option in a find to limit the data you want to retrieve. If you don't want to do this every time you write a find, you can add something like this to your models beforeFind() callback, which will automatically populate the fields options with all fields except the longtext field:
function beforeFind($query) {
if (!isset($query['fields'])) {
foreach ($this->_schema as $field => $foo) {
if ($field == 'longtextfield') {
continue;
}
$query['fields'][] = $this->alias . '.' . $field;
}
}
return $query;
}
Regarding comment:
That's true… The easiest way in this case is probably to unset the field from the schema.
unset($this->Model->_schema['longtextfield']);
I haven't tested it, but this should prevent the field from being included in the query. If you want to make this switchable for each query, you could move it to another variable like $Model->_schemaInactiveFields and move it back when needed. You could even make a Behavior for this.
The parameter fields may help you.It doesn't ignore fields but specifies fields you want:
array(
'conditions' => array('Model.field' => $thisValue), //array of conditions
'fields' => array('Model.field1', 'Model.field2'), //list columns you want
)
You can get more information of retrieving data in the cookbook .
Another idea:
Define your special query in the model:
function myfind($type,$params)
{
$params['fields'] = array('Model.field1','Model.field2',...);
return $this->find($type,$params);
}
Then use it in the controller
$this->Model->myfind($type,$params);
Also try containable behaviour will strip out all unwanted fields and works on model associations as well.
Containable
class Post extends AppModel { <br>
var $actsAs = array('Containable'); <br>
}
where Post is your model?
You can add a beforeFilter function in your Table and add a select to the query
Excample:
public function beforeFind(Event $event, Query $query){
$protected = $this->newEntity()->hidden;
$tableSchema = $event->subject()->schema();
$fields = $tableSchema->columns();
foreach($fields as $key => $name){
if(in_array($name,$protected)){
unset($fields[$key]);
}
}
$query->select($fields);
return $event;
}
In this excample I took the hidden fields from the ModelClass to exclude from result.
Took it from my answer to a simular question here : Hidden fields are still listed from database in cakephp 3

Resources