Using cakePHP's Hash class to extract data from an array - cakephp-2.0

I have an array like this:
Array(
[Rating] => Array(
[0] => Array(
[id] => 4
[rating] => -1
),
[1] => Array(
[id] => 14
[rating] => 9.7
),
[2] => Array(
[id] => 26
[rating] => 9.55
)
)
)
I need to extract all the ratings >= 0 from this array, and JUST the numbers. I was doing this fine with Set::extract('/Rating/rating[rating>-1]', $video)
But I then learned that this is deprecated and you are supposed to use the Hash class now. So I looked up the new syntax and wrote:
Hash::extract($video, 'Rating.{n}.rating[rating>-1]');
which gives me the correct result, but it gives an annoying warning: Warning (4096): Argument 1 passed to Hash::_matches() must be an array, string given, called in E:\www\lib\Cake\Utility\Hash.php on line 131 and defined [CORE\Cake\Utility\Hash.php, line 170]
The warning doesn't occur when I don't have the condition in there (the [rating>-1]) but of course includes the -1's, which I don't want. What am I doing wrong here? Am I misusing this function somehow? Should I just use the deprecated Set class? Or should I just ignore the warning because it won't show up once I put this app into production mode?

This question is old and I'm sure it's already resolved, but the CakePHP documentation is a bit unclear regarding usage of the Hash path syntax. So, in case someone else has come across this, here's what I found.
The syntax in the question is not correct. The correct syntax is 'Hash::extract($ratings, 'Rating.{n}[rating>-1].rating')'.
According to the documentation: "Tokens are composed of two groups. Expressions, are used to traverse the array data, while matchers are used to qualify elements.".
{n}[rating>-1] is considered one token. {n} is the expression which filters the array keys, in this case the key must be numeric. [rating>-1] is the matcher which filters the array elements, in this case the element must be an array that contains a key named rating and an associated value that is greater than -1. Once you have the array element then you can get the rating.
$ratings = array(
'Rating' => array(
array(
'id' => 4,
'rating' => -1
),
array(
'id' => 14,
'rating' => 9.7
),
array(
'id' => 26,
'rating' => 9.55
)
)
);
print_r( Hash::extract($ratings, 'Rating.{n}[rating>-1].rating') );
Results in:
Array ( [0] => 9.7 [1] => 9.55 )

Related

findall ignoring order parameter?

I'm trying to use the following:
$this->Chapter->recursive=1;
$chaps = $this->Chapter->find('all', array(
'order'=> array('sequence_number' => 'ASC')
));
$this->set('chapters', $chaps );
to retrieve all my chapters by increasing order, but CakePHP seems to be ignoring the 'order' parameter. I believe that I have the syntax correct (based on view-source:http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types, which says the following should work:
public function index() {
$articles = $this->Article->find('available', array(
'order' => array('created' => 'desc')
));
}
). The SQL for the table looks like:
CREATE TABLE chapters (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sequence_number INT UNSIGNED,
title VARCHAR(50)
);
and I'm not getting any syntax or run-time errors. However, the SQL generated by Cake to actually get the chapter records is:
SELECT `Chapter`.`id`, `Chapter`.`sequence_number`, `Chapter`.`title`
FROM `Tutorial`.`chapters` AS `Chapter` WHERE 1 = 1
Clearly I'm doing something wrong, but I don't know what it is.
As a work-around I'm happy to put an order property on the Model. Since I typically want to retrieve chapters by sequence number I'm fine with adding this to the model:
public $order = 'Chapter.sequence_number ASC';
Once I do that Cake generates
SELECT `Chapter`.`id`, `Chapter`.`sequence_number`, `Chapter`.`title`
FROM `Tutorial`.`chapters` AS `Chapter` WHERE 1 = 1
ORDER BY `Chapter`.`sequence_number` ASC
What if you try something like this, instead:
$chaps = $this->Chapter->find('all', array(
'order' => array('Chapter.sequence_number ASC')
));
The main difference being this part: Chapter.sequence_number ASC

CakePHP HABTM recursive issue

To explain the issue I'm having, I'll use an example. Let's say that I'm building a system where students can sign up for an afterschool class, but an administrator has to approve the sign-up in order for it to be valid. So I have these models:
Calendar (belongs to "Teacher", hasAndBelongsToMany "Student")
Student (hasAndBelongsToMany "Calendar")
Teacher (hasMany "Calendar")
Now let's say I want to see a list of all of the unapproved sign-ups that are for ninth-graders. I want the list to include the calendar date, the student's name, and the teacher's name. I would do something like this:
$this->request->data = $this->Student->CalendarsStudent->find('all', array(
'conditions' => array(
'CalendarsStudent.is_approved' => null,
'Student.grade' => 9
)
));
The problem with the above code is that the returned array is missing the teacher's name:
Array
(
[0] => Array
(
[CalendarsStudent] => Array
(
[id] => 1274
[calendar_id] => 200
[student_id] => 872
[is_approved] =>
)
[Calendar] => Array
(
[id] => 200
[date] => 2012-12-17
[teacher_id] => 1
[total_slots] => 15
)
[Student] => Array
(
[id] => 872
[teacher_id] => 1
[first_name] => Billy
[last_name] => Smith
[grade] => 9
)
)
)
If I add 'recursive' => 2 to the find parameters, I get way too much information. $this->request->data[0]['Calendar'] will have [Teacher], which is what I want, but it will also have [Student], which I don't want. Also, $this->request->data[0]['Student'] will have subarrays that I don't want. It seems like Containable would fix this, but I can't get that to work either.
Any ideas?
Use CakePHP's [Containable Behavior]. It's amazing - easy to use and will return whatever you want it to.
Side note: If you're using "recursive" as anything other than -1 for ANYTHING, it should be a red flag. Containable is the absolute way to go as it lets you specify easily and exactly what data you want returned. Recursive will often cause memory issues and or other badness.
Best practice IMO: set $recursive = -1; in your AppModel.php and never set it to anything else...ever.

CakePhp 2 tree behaviour's generateTreeList() returns flat list

I followed the docs at the book to use the tree behaviour.
Everything looks ok. parent_id, lft and rght are saved properly but
when I call:
$this->Model->generateTreeList()
The returned list is flat, i.e.:
array(
(int) 8 => 'p1',
(int) 11 => 'child of p1',
(int) 9 => 'p2',
(int) 2 => 'child of p2',
)
Is there anything else I need to be aware of?
It works as expected (according to documentation). generateTreeList returns array where key is id and diplay field is the value.
generateTreeList($conditions=null, $keyPath=null, $valuePath=null, $spacer= '_', $recursive=null)
You can specify $spacer parameter, and have result like i.ex:
array(
1 => 'p1',
2 => '_p2',
3 => '_p3',
4 => '_p4',
5 => '__p5',
);
The list is "flat" but with spacer. If you want to have hierarchical nested array you have to use: $this->Model->find('threaded')

Cake PHP complex find 'OR' is not working properly with null and empty string

Cake PHP complex find 'OR' opertor is not working properly with null...
$conditions = array(
'Person.id' => array(2, 4, 1, 23, 45, 11),
'OR' => array(
array(
array('NOT' => array('Person.image' => null)),
array('NOT' => array('Person.image' => '')),
),
array(
array('NOT' => array('Person.photos' => null)),
array('NOT' => array('Person.photos' => '')),
)
)
);
The corresponding cake sql dump output query as below
SELECT `Person`.`id`, `Person`.`created`, `Person`.`modified`
FROM `people` AS `Person` WHERE `Person`.`id` IN (2, 4, 1, 23, 45, 11) AND
((((NOT (`Person`.`image` IS NULL)) AND (NOT (`Person`.`image` = NULL)))) OR
(((NOT (`Person`.`photos` IS NULL)) AND (NOT (`Person`.`photos` = '')))))
ORDER BY FIELD(`Person`.`id`, 2, 4, 1, 23, 45, 11) ASC LIMIT 3
In cake condition array, I have given Person.image is not null or '', but corresponding cake sql outputs as (NOT (Person.image IS NULL)) AND (NOT (Person.image = NULL)) where it should be like (NOT (Person.image IS NULL)) AND (NOT (Person.image = '')) .
Here Person.image both are compared with NULL itself(IS NULL and = NULL), where Person.image = NULL want to compared with empty string like Person.image = ''.
Here 'Person.image' is 'INT' and 'Person.photos' is 'VARCHAR' of type, but it is difficult to change type from current stage.
How it can be corrected ?
You are not forced to use an associative array to define conditions. You can define it like so:
$conditions = array(
'Person.id' => array(2, 4, 1, 23, 45, 11),
'OR' => array(
array(
array('NOT' => array('Person.image' => null)),
array('NOT' => array('Person.image = "" ')),
),
array(
array('NOT' => array('Person.photos' => null)),
array('NOT' => array('Person.photos = "" ')),
)
)
);
Perhaps there is an issue with interpreting 'Person.image <>' => null, since it is not strictly valid SQL (at least not in MySQL).
From the MySQL manual:
You cannot use arithmetic comparison operators such as =, <, or <> to test for NULL.
You could try replacing it with a negated null comparison:
array('NOT' => array('Person.image' => null))
Aside from that, your last two OR operators does nothing, since you only have one statement in each. Is your array nesting what you ment it to be? If you add newlines and indentation, your code becomes
$conditions = array(
'Person.id' => array(2, 4, 1, 23, 45, 11),
'OR' => array(
array(
'OR' => array(
'Person.image <>' => null
),
array(
'Person.image <>' => ''
)
),
array(
'OR' => array(
'Person.photos <>' => ''
),
array(
'Person.photos <>' => null
)
)
)
);
...which looks weird.
The full change would be:
$conditions = array(
'Person.id' => array(2, 4, 1, 23, 45, 11),
'OR' => array(
array(
array('NOT' => array('Person.image' => null)),
array('NOT' => array('Person.image' => '')),
),
array(
array('NOT' => array('Person.photos' => null)),
array('NOT' => array('Person.photos' => '')),
)
)
);
It appears that Cake's null comparison depends on the database field type. If your field is of type integer then Cake will redefine '' as Null. Varchars will not be redefined.
Are you able to go to MySQL and change the Person.image type to varchar?
If so remember to clear your cache in app/tmp/cache/models for Cake to register changes.
UPDATE: manually changing the schema type will force cake to treat a field type as defined. So something like this prior to the query will work:
$this->Person->_schema['image']['type']='string';
Sorry, those answers are in my opinion not 100% right, they provide workarounds but no solution.
The problem are the database column types:
Person.image is INT and Person.photos is VARCHAR of type
Well, '' (the empty string) is not a valid value for an integer, ever. I assume you have allowed NULL values on the Person.image column, so cake does the right job!
It just doesn't make any sense to ask if an integer field has the value of the empty string, it can only be NULL, 0, 1, -1, 2, -2, etc. (if signed).
What happens here is that cake knows about your data structure (note the DESCRIBE queries when the debug flag is > 0), so it tries to correct your statement to the next useful one - testing for NULL values. Moreover, even if you ask MySQL (or force cake doing so) to query your DB, there will never be any effect because of this condition (maybe because of the other ones):
SELECT * FROM table WHERE my_int_column = '' is completely legit SQL but will always return 0 results whereas SELECT * FROM table WHERE NOT(my_int_column = '') will always return all results.
Conclusion: if your app works by now with the ='' condition, it will work exactly the same without the condition.
Final note: However, you can INSERT the empty string into an integer column and it will be converted by MySQL to 0, so you might want to check for those.

convert query() to find() cakephp

$v = array(1,11.38,15.8);
$sortByPrice = $this->Product->query
(
"SELECT *,
CASE `currency`
WHEN '1' THEN $v[0]
WHEN '2' THEN $v[1]
WHEN '3' THEN $v[2]
END AS 'ratio'
FROM products
ORDER BY price*ratio DESC
"
);
i want to convert what is above to a find function
i tried something like that(but it do not work)..
$v = array(1,11.38,15.8);
$bla = $this->Product->find('all', array(
'conditions' => array(
'Product.currency',
'((
CASE WHEN
Product.currency=1 THEN $v[0]
Product.currency=2 THEN $v[1]
Product.currency=3 THEN $v[2]
END
)) AS ratio'),
'order' => 'ratio',
'limit' => 10
));
can somebody to convert query into find
You are putting into the conditions key. Move it into the fields key or make it a virtualField - that may work.
Edit: also $v[0] in single quotes will not actually replace it with the variable - it will just appear as that text.

Resources