Generate an OR SQL statement in CakePHP 3 - cakephp

I am converting some finds from cakephp2 to cakephp3.
I need to search on a first name and surname on a tutors table that are stored on different columns. According to the docs I needed to have a LEFT join bewtween the tables. I have used an or condition with 2 fields but it works like and 'and' condition if both parameters have a value.
My issue is
q1) I cant get the data with just the first name only , and the surname is null.
q2) I need to pass both first name and surname to get just those data with that name.
Not sure how to do this in cakephp3.
eg $a5='fred'; //just want all first names like fred
$a6=null; //sometimes this will be filled
$a3='2015-05-30';
$a4='2016-06-01';
$query3 = $this->Lessons->find()
->contain(['Tutors'])
->select(['lessons.id','lessons.lesson_date','tutors.id','tutors.first_name','tutors.last_name' ])
->where(['Lessons.lesson_date >' => $a3,'Lessons.lesson_date <' => $a4,
'OR' => [['tutors.first_name like' => '%'.$a5.'%'], ['tutors.last_name like' => '%'.$a6.'%']],
]);
foreach ( $query3 as $row) {
debug($row->toArray());
}
I didnt understand the docs on this point.
http://book.cakephp.org/3.0/en/orm/query-builder.html#advanced-conditions
UPDATE- tried this and this also just gives all the data with either 'at' or 'to' but it should be any names with both 'at' and 'to' in them.
$query3 = $this->Lessons->find()
->contain(['Tutors','Subjects', 'TutoringTypes','Terms','Students'])
->select(['lessons.id','lessons.lesson_date','tutors.id','tutors.first_name','tutors.last_name',
'subjects.id','subjects.name','terms.id','terms.title'])
->where(['Lessons.lesson_date >' => $a3,'Lessons.lesson_date <' => $a4])
->orWhere(function ($exp) {
return $exp->and_([
'tutors.first_name like' => '%an%',
'tutors.last_name like' => '%to%',
]);
});

Pay attention to the generated SQL. You either see it in DebugKit or debug it by calling $query->sql(). You're building a wrong query:
You generate OR ((... AND ...)) because you're using an and_. You probably want ... OR ....
->orWhere(function ($exp) {
return $exp->and_([
'tutors.first_name like' => '%an%',
'tutors.last_name like' => '%to%',
]);
});
You probably want OR. But passing the array directly to orWhere() would work as well.

Related

How to get contain clause to generate the correct code in CakePHP

I have the following code in a Table definition.
$paginationQuery = $this->find()
->select([
'Members__id','Members__member_type','Members__first_name',
'Members__middle_name','Members__last_name','Members__suffix'
])
->contain([
'SocialMembers' => [
'foreignKey' => false,
'queryBuilder' => function (Query $q) {
return $q->where([
'Members.Members__id' => 'SocialMembers.full_member_id'
]);
}
]
])
->from([
$this->getAlias() => $query
])
->order([
'Members__last_name' => 'ASC',
'Members__first_name' => 'ASC'
]);
return $paginationQuery;
This is to paginate the results of a union of two sets of extracted data.
The problem comes from the queryBuilder function. The left join that is generated looks like this:
LEFT JOIN members SocialMembers ON (
SocialMembers.member_type = 2
AND Members.Members__id = 'SocialMembers.full_member_id'
)
There is an unneeded pair of single quotes around SocialMembers.full_member_id. queryBuilder appears to correctly handle the Members.Members__id, but not the value field of the array. Is there any way to get this to generate correctly?
Was able to resolve this by moving the contains clause to the preceding finder methods.

cakephp query builder not working

I have a query
$p = $this->Products
->findById($id)
->select(['name'])
->contain(['Categories.Sizes' => function($q) {
return $q->select(['id', 'name']);
}
]);
which is only returning product's name and not the Size of the product's category. But if the remove the select function which accepts the field names then it delivers also the Sizes
Is there any solution for that?
According to the CakePHP 3 book, in the area "Selecting Rows From A Table", you can specify which fields you want returned by including them in a select array:
$query = $articles
->find()
->select(['id', 'name']) // <-- Notice this line
->where(['id !=' => 1])
->order(['created' => 'DESC']);
So for yours, you're limiting the fields that are returned because you're specifying that you only want the 'name' field.

CakePHP 3.2 find first with where

I'm working with CakePHP 3.2. Here is my find query:
$a=$this->A->find('all', ['where' => [ 'b=='=>5, 'c>'=>1000 ]])->first();
// For debuging what I receive:
$data=$a->toArray();
print_r($data);
I'd like to select first record in 'A', whose column 'b' equals 5 and column 'c' is grater than 1000. What am I doing incorrectly? The above query returns all records from 'A' table.
Use conditions instead of where
$a=$this->A->find('all', ['conditions' => ['b'=>5, 'c >'=>1000]])->first();
simply
$a = $this->A->find()
->where([ 'b' => 5, 'c >' => 1000 ])
->first();
debug($a)
you don't need to call toArray() since first already returns a single entity
It should be like this:
$data = $this->A->find()->where(['b' => 5, 'c >' => 1000])->first();
Use conditions instead of where
$query = $articles->find('all', [
'conditions' => ['Articles.title LIKE' => '%Ovens%']
]);
$result = $query->toArray();

CakePHP/Croogo: How to search in translated nodes too

In croogo's search-View from the Nodes-controller the results include only nodes, where the searched term was found on the Node's model fields, but will skip any translated content for the nodes.
I'm trying to override this functionality and add support for translated content as well, but having a hard time to override the search-view without having to override the whole node-controller.
Does anyone has done this before and could give me some advice, as which approach could I take?
For anyone interested, I ended up doing the following:
Add a new SearchController on my Plugin with a show-view, which is an adapted version of the search-View on the NodesController.
Override the search/* route with my new view.
In order to search in the translated fields, I came with 2 possible approaches:
1) If using only the paginate for this, then there would be necessary to join the node and the i18n tables, first And then look for the string additionally on the joined fields.
2) Or, first find all distinct nodes with matching content on the i18n table through a query. And then use this list of nodes to add condition of "OR Node.id in (list_of_nodes)"
I ended up with alternative 2, and this is how the show-view looks like:
public function show() {
if (!isset($this->request->params['named']['q'])) {
$this->redirect('/');
}
App::uses('Sanitize', 'Utility');
$q = Sanitize::clean($this->request->params['named']['q']);
$results = $this->Node->query(
"SELECT DISTINCT(foreign_key) FROM `i18n` " .
"WHERE content LIKE '%" . $q . "%';");
$node_ids = array();
foreach($results as $res) {
$node_ids[] = $res['i18n']['foreign_key'];
}
$this->paginate['Node']['order'] = 'Node.created DESC';
$this->paginate['Node']['limit'] = Configure::read('Reading.nodes_per_page');
$this->paginate['Node']['conditions'] = array(
'Node.status' => 1,
'AND' => array(
array(
'OR' => array(
'Node.title LIKE' => '%' . $q . '%',
'Node.excerpt LIKE' => '%' . $q . '%',
'Node.body LIKE' => '%' . $q . '%',
'Node.id' => $node_ids,
),
),
array(
'OR' => array(
'Node.visibility_roles' => '',
'Node.visibility_roles LIKE' => '%"' . $this->Croogo->roleId . '"%',
),
),
),
);
// some more stuff ...
}

Cakephp 2 additional joins for "threaded" query

I am using the tree behavior and when I query via ->find('threaded', ...) - as expected - I get the tree back.
But I want additional joins happen, so something like:
$data = $this->Category->find('threaded', array(
'joins' => array(
array('table' => 'videos',
'alias' => 'Video',
'type' => 'LEFT',
'conditions' => array(
'Category.id = Video.category_id',
)
)
)
));
Category hasMany Video, but Video is not a tree, just related.
Can I use the threaded query for that?
To produce the "threaded" output Cake 1) calls a find('all), then 2) puts the resulting array through Set::nest() function.
So just get your output using a standard find + your custom joins, then just use the Set::nest
(NB: Hash has replaced Set in Cake 2, but Cake still uses Set internally. Both will work for now. Hash::nest )
So if you take a look at Cake's model.php, the nest function is called like so:
return Set::nest($results, array(
'idPath' => '/' . $this->alias . '/' . $this->primaryKey,
'parentPath' => '/' . $this->alias . '/' . $parent
));
Use that as a template for your call. For your data it would look something like:
return Set::nest($results, array(
'idPath' => '/Category/id',
'parentPath' => '/Category/parent_id' ));

Resources