Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CakePHP 2.x: Is the $primary flag on Model::afterFind() actually useful?

Tags:

cakephp

CakePHP's Model::afterFind() callback looks like:

afterFind(array $results, boolean $primary = false)

According to the documentation:

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.

They can differ, but experimentation shows that they don't always differ. As far as I can tell, the $primary parameter isn't actually all that useful. If it's set to false you may or may not get a flattened data structure, so you may or may not wind up with the dreaded "cannot use string offset as an array" error message.

Although I haven't tried it yet, my thought based on the documentation was to ignore the $primary flag altogether and just check the data:

public function afterFind($results, $primary = false) {
  if (array_key_exists(0, $results) {
    // operate on $results[0]['User']['fieldname']
  } else {
    // operate on $results['fieldname']
  }
  return $results;
}

This is hackish and I don't like it, but it seems likely to be more useful than $primary.

Explicitly stated, my questions are:

  1. What is the $primary flag actually useful for?
  2. Am I correct that it is not useful for determining the structure of the $results array, or have I missed something there?
like image 200
eaj Avatar asked Dec 31 '12 21:12

eaj


2 Answers

Indeed the $primary parameter seems to only be useful in warning you of cases where the format of $results is unpredictable. It is not useful in determining the format of $results.

More information here: https://groups.google.com/forum/?fromgroups=#!topic/cake-php/Mqufi67UoFo

The solution offered there is to check !isset($results[$this->primaryKey]) to see what format $results is. This is also a bit of a hack, but arguably better than checking for a key '0'.

The solution I ultimately came up with is to do something like this:

public function afterFind($results, $useless) {

    // check for the primaryKey field
    if(!isset($results[$this->primaryKey])) {
        // standard format, use the array directly
        $resultsArray =& $results;
    } else {
        // stupid format, create a dummy array
        $resultsArray = array(array());
        // and push a reference to the single value into our array
        $resultsArray[0][$this->alias] =& $results;
    }

    // iterate through $resultsArray
    foreach($resultsArray as &$result) {
        // operate on $result[$this->alias]['fieldname']
        // one piece of code for both cases. yay!
    }

    // return $results in whichever format it came in
    // as but with the values modified by reference
    return parent::afterFind($results, $useless);
}

This reduces code duplication because you don't have to write your field alteration logic twice (once for an array and once for non-array).

You may be able to avoid the references stuff altogether by just returning $resultsArray at the end of the method, but I wasn't sure what issues that might cause if CakePHP (or some other parent class) expects $results in the way it was passed in. Plus this way doesn't have the overhead of copying the $results array.

like image 94
Code Commander Avatar answered Nov 14 '22 18:11

Code Commander


If you can't always rely on primaryKey being in the fields list AND you know the key you are looking for, you can get away with something a bit more simple. Here is an example:

/**
 * Decrypt password
 *
 * @see Model::afterFind()
 */
public function afterFind($results, $primary = false) {        
    if (!empty($results['password'])) {
        $results['password'] = Security::rijndael($results['password'], Configure::read('encrypt.key'), 'decrypt');
        return $results;
    }

    foreach ($results as &$r) {
        if (!empty($r[$this->alias]['password'])) {
            $r[$this->alias]['password'] = Security::rijndael($r[$this->alias]['password'], Configure::read('encrypt.key'), 'decrypt');
        }
    }
    return $results;
}
like image 26
rynop Avatar answered Nov 14 '22 18:11

rynop