Fluent Arrays and Strings in PHP – Adding JSON and more


Recently I’ve had the need to extend the interface further, among other things with JSON support. I’ve used code published by Nicolas Crovatti to do this. There was one line in there though that wasn’t consistent with its purpose. If you use that code you have to replace if($i != $keys[$i]) with if($i !== $keys[$i]). On the other hand if you use PHP >= 5.2 you don’t need this function at all, you use json_encode instead.

function merCampaigns($sdate, $edate){
  $sql = "SELECT * FROM statistics WHERE stamp > $sdate AND stamp < $edate AND camp_id 
      IN(SELECT id FROM campaigns WHERE merchant_id = {$this->usr_id}) ORDER BY stamp";
  $l = $this->getConfig('legends');
  return Arr::fluent2d($this->db->fetchAll($sql))
    ->apply2field('date', 'stamp', "d", '?')
    ->sum('clicks,actions,impressions,sales', 'stamp')
    ->plot(array($l['clicks'],$l['leads'],$l['impressions'],$l['sales']))->json();
}

The above is a usage example from my current project. It will query a table of statistics for a 2D array, make it fluent, convert all stamps to the current day of the month, sum all clicks etc, create a plot array with the help of the resultant data and some extra labels and finally output the JSON for use with jQuery Flot. If you read that tutorial you will see that I’ve opted to do more in Javascript instead of in PHP, the above is still a good example of how a JSON to be used with jFlot could look like and be built, especially if you’re allergic to Javascript.

Let’s walk the chain, first apply2field in Arr.php:

function apply2field(){
  $params = func_get_args();
  $f 		= array_shift($params);
  $field 	= array_shift($params);
  foreach($this->c as &$sub){
    $cur_params 	= $this->fill_ret($params, $sub->c[$field]);
    $sub->c[$field] = call_user_func_array($f, $cur_params);
  }
  return $this;
}

And in FluentExt.php:

protected function fill_ret($arr, $with = false){
  foreach($arr as $a){
    if($a == '?') 	$ret[] = $with == false ? $this->c : $with;
    else			$ret[] = $a;
  }
  return $ret;
}

The use of array_shift explains the first two arguments ‘date’ and ‘stamp’ above. We then loop the container by reference, replace the placeholder with the help of the current sub-array and the field name, finally we reassign the result to the field by calling the function with call_user_func_array.

function sum($keys, $ctrlKey = false){
  if($this->d == 1){
    return array_sum($this->c);
  }else{
    $keys = explode(',', $keys);
    if(!empty($ctrlKey)){
      foreach($this->c as $sub){
        foreach($keys as $key){
          if(isset($res[ $sub->c[$ctrlKey] ][$key]))
            $res[ $sub->c[$ctrlKey] ][$key] += $sub->c[$key];
          else
            $res[ $sub->c[$ctrlKey] ][$key] = $sub->c[$key];
        }
      }
      return $this->ret($res)->make2d();
    }else{
      foreach($this->c as $sub){
        foreach($keys as $key){
          if(isset($res[$key])) 	$res[$key] += $sub->c[$key];
          else					$res[$key] = $sub->c[$key];
        }
      }
      return $this->ret($res);
    }
  }
}

If we pass a control key that key will be used to sum by, in our case we sum for instance all clicks on day 02, 03 etc and keep the results as sub-arrays in a 2D result. If not we simply sum the whole table and the result will be a flat array of sums.

function plot($labels = false){
  foreach($this->c as $key => $sub){
    foreach($sub->c as $subKey => $value)
      $res[$subKey][] = array($key, $value);
  }
  if(!$labels)
    return $this->ret($res)->numeric();
  else{
    foreach($res as $sub)
      $ret[] = array("label" => array_shift($labels), "data" => $sub);
    return $this->ret($ret);
  }
}

function numeric(){
  foreach($this->c as $sub) $res[] = $sub;
  return $this->ret($res);
}

The only purpose this method has is to create an array which when JSON encoded will be accepted as data when calling Flot. That’s the purpose it has for now, for me, at the moment anyway. Now it also becomes apparent why the control key ended up being the key for each sub-array of sums after summing. The key is now used as the first value in each x,y pair. You can already see that the days 02, 03… will end up on the x axis in our case.

That leaves us with the JSON method which you can check out by going to Nicolas page I linked to above. Except for fixing the bug the only other real change is at the top:

function json($arr = false) {
    	  $arr 			= !$arr ? $this->c : $arr;
. . .

So when we call the function for the first time with an empty value $arr will be false and therefore get the default value of the current container. And yeah, as I said above, if you use PHP 5.2 you can make this method a lot shorter.

Test:

$arr = array(
	array('id' => '1', 'stamp' => 1199120400, 'apples' => 2, 'oranges' => 4),
	array('id' => '1', 'stamp' => 1199206800, 'apples' => 7, 'oranges' => 5),
	array('id' => '2', 'stamp' => 1199293200, 'apples' => 3, 'oranges' => 9),
	array('id' => '2', 'stamp' => 1199379600, 'apples' => 10, 'oranges' => 1)
);

echo Arr::fluent2d($arr)->apply2field('date', 'stamp', "d", '?')->sum('apples,oranges', 'stamp')
		->plot(array('Apples','Oranges'))->json();

Output:

[
  {"label":"Apples","data":[[01,2],[02,7],[03,3],[04,10]]},
  {"label":"Oranges","data":[[01,4],[02,5],[03,9],[04,1]]}
]

The code has been updated.

Related Posts

Tags: , ,