Framework  3.9
pivot.inc
Go to the documentation of this file.
1 <?php
5 /**************************************************************
6 
7 Copyright (c) 2013 Sonjara, Inc
8 
9 Permission is hereby granted, free of charge, to any person
10 obtaining a copy of this software and associated documentation
11 files (the "Software"), to deal in the Software without
12 restriction, including without limitation the rights to use,
13 copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the
15 Software is furnished to do so, subject to the following
16 conditions:
17 
18 The above copyright notice and this permission notice shall be
19 included in all copies or substantial portions of the Software.
20 
21 Except as contained in this notice, the name(s) of the above
22 copyright holders shall not be used in advertising or otherwise
23 to promote the sale, use or other dealings in this Software
24 without prior written authorization.
25 
26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
28 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
31 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
33 OTHER DEALINGS IN THE SOFTWARE.
34 
35 *****************************************************************/
36 
50 abstract class AbstractPivotQuery extends AbstractQuery
51 {
54  var $baseItem;
55 
58 
61 
63 
64  var $fields;
66 
67  var $additionalFields = array();
68  var $groupByFields = array();
69 
70  var $orderBy;
71  var $totalField = null;
72 
78  function __construct($base, $baseConstraint = "")
79  {
80  $this->baseClass = $base;
81  $this->baseConstraint = $baseConstraint;
82 
83  $this->baseItem = new $this->baseClass;
84 
85  foreach($this->baseItem->getFields() as $field => $type)
86  {
87  $this->baseConstraint = preg_replace("/\\b".$field."\\b/i", "b.$field", $this->baseConstraint);
88  }
89 
90  $matches = array();
91 
92  if (preg_match("/ORDER BY.*/i", $this->baseConstraint, $matches))
93  {
94  $this->orderBy = $matches[0];
95  $this->baseConstraint = str_replace($this->orderBy, "", $this->baseConstraint);
96  }
97 
98  $this->pivotItem = null;
99 
100  $this->page = -1;
101  $this->size = -1;
102 
103  $basePK = $this->baseItem->getPrimaryKey();
104  $this->fields[$basePK] = $this->baseItem->getType($basePK);
105 
106  $this->additionalFields[] = "b.{$basePK} as {$basePK}";
107  $this->groupByFields[] = "b.{$basePK}";
108  }
109 
117  function pivot($pivotClass, $pivotConstraint, $pivotFunction = "SUM")
118  {
119  $this->pivotClass = $pivotClass;
120  $this->pivotConstraint = $pivotConstraint;
121 
122  $this->pivotItem = new $this->pivotClass;
123 
124  foreach($this->pivotItem->getFields() as $field => $type)
125  {
126  $this->pivotConstraint = preg_replace("/\\b".$field."\\b/i", "p.$field", $this->pivotConstraint);
127  }
128 
129  return $this;
130  }
131 
139  {
140  $this->rangeClass = $rangeClass;
141  $this->rangeConstraint = $rangeConstraint;
142  return $this;
143  }
144 
154  function field($field, $alias = "", $type = String)
155  {
156  $field = preg_replace("/\\b".$this->baseClass."\\b/i", "b", $field);
157  $field = preg_replace("/\\b".$this->pivotClass."\\b/i", "p", $field);
158 
159  $bareField = preg_replace("/^.*?\\./", "", $field);
160 
161  $this->fields[$bareField] = String;
162  $this->additionalFields[] = "$field as $bareField";
163  $this->groupByFields[] = $field;
164 
165  return $this;
166  }
167 
179  function pivotField($format, $value, $alias = null, $expression = "SUM", $type = Number)
180  {
181  if (!$alias) $alias = $format;
182 
183  $this->fieldNameFormats[$format] = array("expression" => $expression, "alias" => $alias, "value" => $value, "type" => $type);
184  return $this;
185  }
186 
192  protected function generateFieldExpressions()
193  {
194  if (!$this->rangeClass)
195  {
196  if (!$this->pivotClass)
197  {
198  throw new FakoliExpression("No Range or Pivot class provided");
199  }
200 
201  $this->rangeClass = $this->pivotClass;
202  $this->rangeConstaint = $this->pivotConstraint;
203  }
204 
205  $obj = new $this->rangeClass;
206  $pivot = $obj->getPrimaryKey();
207 
208  $rangeItems = Query::create($this->rangeClass, $this->rangeConstraint)->execute();
209 
210  $expressionMap = array();
211 
212  foreach($rangeItems as $item)
213  {
214  foreach($this->fieldNameFormats as $fieldName => $defn)
215  {
216  $fieldName = $item->format($fieldName);
217  $value = $defn["value"];
218  $expression = $defn["expression"];
219  $alias = $defn["alias"];
220 
221  $empty = ($defn["type"] == String) ? "''" : 0;
222 
223  $expressionMap[$fieldName] = "$expression(IF (p.{$pivot}=".$item->quoteFieldValue($pivot).", IFNULL(p.{$value}, {$empty}), {$empty}))";
224  $this->fields[$fieldName] = $defn["type"];
225  $this->fieldAliases[$fieldName] = $item->format($alias);
226  }
227  }
228 
229  return $expressionMap;
230  }
231 
236  protected function generateQuery()
237  {
238  // First generate fields
239 
240  $expressionMap = $this->generateFieldExpressions();
241 
242  $additionalFields = implode(", ", $this->additionalFields);
243  $groupByFields = implode(", ", $this->groupByFields);
244 
245  $base = $this->baseItem;
246  $pivot = $this->pivotItem;
247 
248  $basePK = $base->getPrimaryKey();
249  $pivotPK = $pivot->getPrimaryKey();
250 
251  $query = "SELECT $additionalFields";
252  foreach($expressionMap as $field => $expression)
253  {
254  $query .= ", ";
255  $query .= "\n\t{$expression} as $field";
256  $first = false;
257  }
258 
259  $query .= "\nFROM {$base->table} b left outer join {$pivot->table} p \n";
260 
261  $query .= "ON (p.{$basePK}=b.{$basePK} ";
262 
263  $constraint = preg_replace("/^\\s*WHERE /i", "AND ", $this->pivotConstraint);
264  $query .= " ".$constraint;
265 
266  $constraint = $this->baseConstraint ? $this->baseConstraint : "";
267  $query .= ") $constraint GROUP BY $groupByFields";
268  $query .= " ".$this->orderBy;
269 
270  $this->query = $query;
271  return $query;
272  }
273 
279  function createPivotItem()
280  {
281  return new PivotItem($this->fields, $this->baseClass, $this->baseItem->getPrimaryKey(), $this->fieldAliases);
282  }
283 
287  function additionalFields()
288  {
289  for($i = 0; $i < func_num_args(); ++$i)
290  {
291  $field = func_get_arg($i);
292  $type = $this->baseItem->getType($field);
293  if ($type)
294  {
295  $this->fields[$field] = $type;
296  $this->additionalFields[] = "b.{$field} as {$field}";
297  }
298  }
299  return $this;
300  }
301 }
302 
308 {
309  var $page;
310  var $size;
311 
312  function __construct($base, $baseConstraint = "")
313  {
314  parent::__construct($base, $baseConstraint);
315  }
316 
324  function page($page, $size)
325  {
326  $this->page = $page;
327  $this->size = $size;
328  return $this;
329  }
330 
331  static function create($base, $baseConstraint = "")
332  {
333  return new PivotQuery($base, $baseConstraint);
334  }
335 
339  function execute()
340  {
341  $query = $this->generateQuery();
342 
343  trace("$query", 3);
344  trace("Page: $this->page Size: $this->size", 3);
345  $items = array();
346 
347  $size = $this->size;
348 
349  try
350  {
352 
353  $result = $db->prepare($query);
354  $result->execute($this->params);
355 
356  if ($this->page > 0)
357  {
358  $count = ($this->page - 1) * $this->size;
359  while($count--)
360  {
361  $result->fetch();
362  }
363  }
364 
365  while($line = $result->fetch())
366  {
367  $item = $this->createPivotItem();
368  $item->populate($line);
369  $items[] = $item;
370 
371  --$size;
372  if ($size == 0) break;
373  }
374 
375  unset($result);
376  }
377  catch(PDOException $e)
378  {
379  $err = "query() failed - " . $e->getMessage();
380  trace($err, 2);
381  throw new DataItemException($err);
382  }
383 
384  trace(count($items)." items found", 3);
385  return $items;
386  }
387 }
388 
389 
395 {
396  var $indexBy;
397 
398  function __construct($base, $baseConstraint = "", $indexBy = "")
399  {
400  parent::__construct($base, $baseConstraint);
401  $this->indexBy = $indexBy;
402  }
403 
409  function indexBy($indexBy)
410  {
411  $this->indexBy = $indexBy;
412  return $this;
413  }
414 
418  function execute()
419  {
420  $query = $this->generateQuery();
421 
422  trace("$query", 3);
423  $items = array();
424  $field = $this->indexBy;
425 
426  try
427  {
429 
430  $result = $db->prepare($query);
431  $result->execute($this->params);
432 
433  while($line = $result->fetch())
434  {
435  $item = $this->createPivotItem();
436  $item->populate($line);
437  $val = $item->get($field);
438 
439  if (array_key_exists($val, $items))
440  {
441  // Implicitly promote to array if there is a collision
442  if (!is_array($items[$val]))
443  {
444  $items[$val] = array($items[$val]);
445  }
446  $items[$val][] = $item;
447  }
448  else
449  {
450  $items[$val] = $item;
451  }
452  }
453 
454  unset($result);
455  }
456  catch(PDOException $e)
457  {
458  $err = "IndexedPivotQuery failed - " . $e->getMessage();
459  trace($err, 2);
460  throw new DataItemException($err);
461  }
462 
463  return $items;
464  }
465 
473  static function create($base, $baseConstraint = "", $indexBy = "")
474  {
475  return new IndexedPivotQuery($base, $baseConstraint, $indexBy);
476  }
477 }
478 
479 
487 {
488  var $groupBy;
489 
490  public function __construct($base, $baseConstraint = "", $groupBy = "")
491  {
492  parent::__construct($base, $baseConstraint);
493  $this->groupBy = $groupBy;
494  }
495 
501  function groupBy($groupBy)
502  {
503  $this->groupBy = $groupBy;
504  return $this;
505  }
506 
510  function execute()
511  {
512  $query = $this->generateQuery();
513 
514  trace($query, 3);
515 
516  $items = array();
517 
518  $field = $this->groupBy;
519 
520  try
521  {
523 
524  $result = $db->prepare($query);
525  $result->execute($this->params);
526 
527  while($line = $result->fetch())
528  {
529  $item = $this->createPivotItem();
530  $item->populate($line);
531  $items[$item->$field][] = $item;
532  }
533 
534  unset($result);
535  }
536  catch(PDOException $e)
537  {
538  $err = "GroupedPivotQuery failed - " . $e->getMessage();
539  trace($err, 2);
540  throw new DataItemException($err);
541  }
542 
543  return $items;
544  }
545 
553  static function create($class, $constraints = "", $groupBy = "")
554  {
556  }
557 }
558 
559 
567 class PivotItem extends DataItem
568 {
569  var $fields = array();
570  var $fieldAliases = array();
571  var $relations = array();
572 
574 
582  function __construct($fields, $baseClass, $primaryKey, $fieldAliases = array())
583  {
584  $this->fields = $fields;
585  $this->primary_key = $primaryKey;
586  $this->baseClass = $baseClass;
587  $this->fieldAliases = $fieldAliases;
588 
589  $this->relations = array($baseClass => $baseClass);
590  parent::__construct();
591  }
592 
597  function __call($method, $args)
598  {
599  // Provide a relation traversal pseudo-method that
600  // allows traversal back to the base object for the
601  // pivot
602 
603  if ($this->baseClass == $method)
604  {
605  return $this->getRelated($this->baseClass);
606  }
607  }
608 
609  function getTotal()
610  {
611  $total = 0.0;
612 
613  foreach($this->fields as $field => $type)
614  {
615  if ($field != $this->primary_key)
616  {
617  $total += $this->get($field);
618  }
619  }
620 
621  return $total;
622  }
623 }
624 
632 {
633  var $pivot;
634  var $table;
635 
636  var $columnSpecs = array();
637 
644  {
645  $this->pivot = $pivot;
646  $this->table = $table;
647  }
648 
661  function column($pattern, $format, $sortable = true, $style = "", $typeHint = null, $onExport = null, $sortFormat = null, $footerTotal = false)
662  {
663  $this->columnSpecs[$pattern] = array("format" => $format, "sortable" => $sortable, "style" => $style,
664  "typeHint" => $typeHint, "onExport" => $onExport, "sortFormat" => $sortFormat,
665  "footerTotal" => $footerTotal);
666  return $this;
667  }
668 
672  function create()
673  {
674  $item = $this->pivot->createPivotItem();
675 
676  $fields = $item->getFields();
677 
678  foreach($fields as $field => $type)
679  {
680  foreach($this->columnSpecs as $spec => $details)
681  {
682  $matches = array();
683  if (preg_match("/{$spec}/", $field, $matches))
684  {
685  $format = str_replace("%FIELD%", $field, $details["format"]);
686 
687  if ($matches[1]) $format = str_replace("%KEY%", $matches[1], $format);
688 
689  $this->table->column($item->prettifyFieldName($field), $format, $details["sortable"], $details["style"],
690  $details["typeHint"], $details["onExport"], $details["sortFormat"]);
691  if ($details["footerTotal"])
692  {
693  $this->table->footerTotal($format);
694  }
695  break;
696  }
697  }
698  }
699  }
700 }
701 ?>
AbstractPivotQuery provides the common base class for the shaped pivot query classes PivotQuery,...
Definition: pivot.inc:51
generateQuery()
Generates SQL for the full pivot query, with grouping.
Definition: pivot.inc:236
pivot($pivotClass, $pivotConstraint, $pivotFunction="SUM")
Adds the pivot to the query.
Definition: pivot.inc:117
generateFieldExpressions()
Builds the expressions for the pivot query.
Definition: pivot.inc:192
createPivotItem()
Creates an empty PivotItem, configured with fields and field aliases based on the pivot and range.
Definition: pivot.inc:279
pivotField($format, $value, $alias=null, $expression="SUM", $type=Number)
Adds a pivot field format to the output object.
Definition: pivot.inc:179
range($rangeClass, $rangeConstraint)
Adds the range to the query.
Definition: pivot.inc:138
additionalFields()
Register additional fields from the base to be included in the PivotItem output.
Definition: pivot.inc:287
__construct($base, $baseConstraint="")
Constructs a new AbstractPivotQuery.
Definition: pivot.inc:78
field($field, $alias="", $type=String)
Adds a fixed field to the output object.
Definition: pivot.inc:154
params($params)
Sets the bound parameters array.
Definition: query.inc:91
static getConnection()
Retrieves a reference to the global database connection.
DataItem is the generic base class for database mapped classes.
Definition: data_item.inc:62
getRelated($class, $field="")
Returns a single item related by the specified foreign key.
Definition: data_item.inc:1210
Performs a pivot query against the database, returning an array of arrays of PivotItems,...
Definition: pivot.inc:487
groupBy($groupBy)
Set the field by which results should be grouped.
Definition: pivot.inc:501
static create($class, $constraints="", $groupBy="")
Create a new GroupedPivotQuery.
Definition: pivot.inc:553
__construct($base, $baseConstraint="", $groupBy="")
Definition: pivot.inc:490
Run a pivot query and return the results indexed by a selected field.
Definition: pivot.inc:395
__construct($base, $baseConstraint="", $indexBy="")
Definition: pivot.inc:398
indexBy($indexBy)
Sets the field by which the results should be indexed.
Definition: pivot.inc:409
static create($base, $baseConstraint="", $indexBy="")
Creates a new IndexedPivotQuery instance.
Definition: pivot.inc:473
PivotItems are dynamically constructed DataItems that map the results of PivotQueries.
Definition: pivot.inc:568
__construct($fields, $baseClass, $primaryKey, $fieldAliases=array())
Creates a new PivotItem.
Definition: pivot.inc:582
$relations
Definition: pivot.inc:571
getTotal()
Definition: pivot.inc:609
__call($method, $args)
Provide a relation pseudo-method that traverses back to the object at the base of the pivot.
Definition: pivot.inc:597
$fieldAliases
Definition: pivot.inc:570
$baseClass
Definition: pivot.inc:573
Standard Pivot Query, with support for paging.
Definition: pivot.inc:308
execute()
Definition: pivot.inc:339
page($page, $size)
Sets the page number and size for constraining the result set by page.
Definition: pivot.inc:324
__construct($base, $baseConstraint="")
Constructs a new AbstractPivotQuery.
Definition: pivot.inc:312
static create($base, $baseConstraint="")
Definition: pivot.inc:331
The PivotTableHelper class provides support for configuring display tables (DataListViews and Grouped...
Definition: pivot.inc:632
create()
Adds columns to the table based on the column specifications that have been provided.
Definition: pivot.inc:672
__construct($pivot, $table)
Creates a new PivotTableHelper.
Definition: pivot.inc:643
column($pattern, $format, $sortable=true, $style="", $typeHint=null, $onExport=null, $sortFormat=null, $footerTotal=false)
Adds a column specification.
Definition: pivot.inc:661
static create($class, $constraints="")
Static factory method to create a new Query.
Definition: query.inc:358
trace($msg, $lvl=3, $callStack=null)
Send output to the trace log.
Definition: functions.inc:1010
query($class)
Performs a query against the database, returning an array of DataItem objects of the specified class.
Definition: query.inc:373