fix calendar css, take 2. (#213)
[openemr.git] / interface / modules / zend_modules / library / Zend / Db / Sql / Select.php
blob2596eb2cc8eb413e63ae69528e813b2b90e4eff7
1 <?php
2 /**
3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
8 */
10 namespace Zend\Db\Sql;
12 use Zend\Db\Adapter\Driver\DriverInterface;
13 use Zend\Db\Adapter\ParameterContainer;
14 use Zend\Db\Adapter\Platform\PlatformInterface;
16 /**
18 * @property Where $where
19 * @property Having $having
21 class Select extends AbstractPreparableSql
23 /**#@+
24 * Constant
25 * @const
27 const SELECT = 'select';
28 const QUANTIFIER = 'quantifier';
29 const COLUMNS = 'columns';
30 const TABLE = 'table';
31 const JOINS = 'joins';
32 const WHERE = 'where';
33 const GROUP = 'group';
34 const HAVING = 'having';
35 const ORDER = 'order';
36 const LIMIT = 'limit';
37 const OFFSET = 'offset';
38 const QUANTIFIER_DISTINCT = 'DISTINCT';
39 const QUANTIFIER_ALL = 'ALL';
40 const JOIN_INNER = 'inner';
41 const JOIN_OUTER = 'outer';
42 const JOIN_LEFT = 'left';
43 const JOIN_RIGHT = 'right';
44 const JOIN_OUTER_RIGHT = 'outer right';
45 const JOIN_OUTER_LEFT = 'outer left';
46 const SQL_STAR = '*';
47 const ORDER_ASCENDING = 'ASC';
48 const ORDER_DESCENDING = 'DESC';
49 const COMBINE = 'combine';
50 const COMBINE_UNION = 'union';
51 const COMBINE_EXCEPT = 'except';
52 const COMBINE_INTERSECT = 'intersect';
53 /**#@-*/
55 /**
56 * @var array Specifications
58 protected $specifications = array(
59 'statementStart' => '%1$s',
60 self::SELECT => array(
61 'SELECT %1$s FROM %2$s' => array(
62 array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
63 null
65 'SELECT %1$s %2$s FROM %3$s' => array(
66 null,
67 array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
68 null
70 'SELECT %1$s' => array(
71 array(1 => '%1$s', 2 => '%1$s AS %2$s', 'combinedby' => ', '),
74 self::JOINS => array(
75 '%1$s' => array(
76 array(3 => '%1$s JOIN %2$s ON %3$s', 'combinedby' => ' ')
79 self::WHERE => 'WHERE %1$s',
80 self::GROUP => array(
81 'GROUP BY %1$s' => array(
82 array(1 => '%1$s', 'combinedby' => ', ')
85 self::HAVING => 'HAVING %1$s',
86 self::ORDER => array(
87 'ORDER BY %1$s' => array(
88 array(1 => '%1$s', 2 => '%1$s %2$s', 'combinedby' => ', ')
91 self::LIMIT => 'LIMIT %1$s',
92 self::OFFSET => 'OFFSET %1$s',
93 'statementEnd' => '%1$s',
94 self::COMBINE => '%1$s ( %2$s )',
97 /**
98 * @var bool
100 protected $tableReadOnly = false;
103 * @var bool
105 protected $prefixColumnsWithTable = true;
108 * @var string|array|TableIdentifier
110 protected $table = null;
113 * @var null|string|Expression
115 protected $quantifier = null;
118 * @var array
120 protected $columns = array(self::SQL_STAR);
123 * @var array
125 protected $joins = array();
128 * @var Where
130 protected $where = null;
133 * @var array
135 protected $order = array();
138 * @var null|array
140 protected $group = null;
143 * @var null|string|array
145 protected $having = null;
148 * @var int|null
150 protected $limit = null;
153 * @var int|null
155 protected $offset = null;
158 * @var array
160 protected $combine = array();
163 * Constructor
165 * @param null|string|array|TableIdentifier $table
167 public function __construct($table = null)
169 if ($table) {
170 $this->from($table);
171 $this->tableReadOnly = true;
174 $this->where = new Where;
175 $this->having = new Having;
179 * Create from clause
181 * @param string|array|TableIdentifier $table
182 * @throws Exception\InvalidArgumentException
183 * @return Select
185 public function from($table)
187 if ($this->tableReadOnly) {
188 throw new Exception\InvalidArgumentException('Since this object was created with a table and/or schema in the constructor, it is read only.');
191 if (!is_string($table) && !is_array($table) && !$table instanceof TableIdentifier) {
192 throw new Exception\InvalidArgumentException('$table must be a string, array, or an instance of TableIdentifier');
195 if (is_array($table) && (!is_string(key($table)) || count($table) !== 1)) {
196 throw new Exception\InvalidArgumentException('from() expects $table as an array is a single element associative array');
199 $this->table = $table;
200 return $this;
204 * @param string|Expression $quantifier DISTINCT|ALL
205 * @return Select
207 public function quantifier($quantifier)
209 if (!is_string($quantifier) && !$quantifier instanceof ExpressionInterface) {
210 throw new Exception\InvalidArgumentException(
211 'Quantifier must be one of DISTINCT, ALL, or some platform specific object implementing ExpressionInterface'
214 $this->quantifier = $quantifier;
215 return $this;
219 * Specify columns from which to select
221 * Possible valid states:
223 * array(*)
225 * array(value, ...)
226 * value can be strings or Expression objects
228 * array(string => value, ...)
229 * key string will be use as alias,
230 * value can be string or Expression objects
232 * @param array $columns
233 * @param bool $prefixColumnsWithTable
234 * @return Select
236 public function columns(array $columns, $prefixColumnsWithTable = true)
238 $this->columns = $columns;
239 $this->prefixColumnsWithTable = (bool) $prefixColumnsWithTable;
240 return $this;
244 * Create join clause
246 * @param string|array $name
247 * @param string $on
248 * @param string|array $columns
249 * @param string $type one of the JOIN_* constants
250 * @throws Exception\InvalidArgumentException
251 * @return Select
253 public function join($name, $on, $columns = self::SQL_STAR, $type = self::JOIN_INNER)
255 if (is_array($name) && (!is_string(key($name)) || count($name) !== 1)) {
256 throw new Exception\InvalidArgumentException(
257 sprintf("join() expects '%s' as an array is a single element associative array", array_shift($name))
260 if (!is_array($columns)) {
261 $columns = array($columns);
263 $this->joins[] = array(
264 'name' => $name,
265 'on' => $on,
266 'columns' => $columns,
267 'type' => $type
269 return $this;
273 * Create where clause
275 * @param Where|\Closure|string|array|Predicate\PredicateInterface $predicate
276 * @param string $combination One of the OP_* constants from Predicate\PredicateSet
277 * @throws Exception\InvalidArgumentException
278 * @return Select
280 public function where($predicate, $combination = Predicate\PredicateSet::OP_AND)
282 if ($predicate instanceof Where) {
283 $this->where = $predicate;
284 } else {
285 $this->where->addPredicates($predicate, $combination);
287 return $this;
290 public function group($group)
292 if (is_array($group)) {
293 foreach ($group as $o) {
294 $this->group[] = $o;
296 } else {
297 $this->group[] = $group;
299 return $this;
303 * Create where clause
305 * @param Where|\Closure|string|array $predicate
306 * @param string $combination One of the OP_* constants from Predicate\PredicateSet
307 * @return Select
309 public function having($predicate, $combination = Predicate\PredicateSet::OP_AND)
311 if ($predicate instanceof Having) {
312 $this->having = $predicate;
313 } else {
314 $this->having->addPredicates($predicate, $combination);
316 return $this;
320 * @param string|array $order
321 * @return Select
323 public function order($order)
325 if (is_string($order)) {
326 if (strpos($order, ',') !== false) {
327 $order = preg_split('#,\s+#', $order);
328 } else {
329 $order = (array) $order;
331 } elseif (!is_array($order)) {
332 $order = array($order);
334 foreach ($order as $k => $v) {
335 if (is_string($k)) {
336 $this->order[$k] = $v;
337 } else {
338 $this->order[] = $v;
341 return $this;
345 * @param int $limit
346 * @return Select
348 public function limit($limit)
350 if (!is_numeric($limit)) {
351 throw new Exception\InvalidArgumentException(sprintf(
352 '%s expects parameter to be numeric, "%s" given',
353 __METHOD__,
354 (is_object($limit) ? get_class($limit) : gettype($limit))
358 $this->limit = $limit;
359 return $this;
363 * @param int $offset
364 * @return Select
366 public function offset($offset)
368 if (!is_numeric($offset)) {
369 throw new Exception\InvalidArgumentException(sprintf(
370 '%s expects parameter to be numeric, "%s" given',
371 __METHOD__,
372 (is_object($offset) ? get_class($offset) : gettype($offset))
376 $this->offset = $offset;
377 return $this;
381 * @param Select $select
382 * @param string $type
383 * @param string $modifier
384 * @return Select
385 * @throws Exception\InvalidArgumentException
387 public function combine(Select $select, $type = self::COMBINE_UNION, $modifier = '')
389 if ($this->combine !== array()) {
390 throw new Exception\InvalidArgumentException('This Select object is already combined and cannot be combined with multiple Selects objects');
392 $this->combine = array(
393 'select' => $select,
394 'type' => $type,
395 'modifier' => $modifier
397 return $this;
401 * @param string $part
402 * @return Select
403 * @throws Exception\InvalidArgumentException
405 public function reset($part)
407 switch ($part) {
408 case self::TABLE:
409 if ($this->tableReadOnly) {
410 throw new Exception\InvalidArgumentException(
411 'Since this object was created with a table and/or schema in the constructor, it is read only.'
414 $this->table = null;
415 break;
416 case self::QUANTIFIER:
417 $this->quantifier = null;
418 break;
419 case self::COLUMNS:
420 $this->columns = array();
421 break;
422 case self::JOINS:
423 $this->joins = array();
424 break;
425 case self::WHERE:
426 $this->where = new Where;
427 break;
428 case self::GROUP:
429 $this->group = null;
430 break;
431 case self::HAVING:
432 $this->having = new Having;
433 break;
434 case self::LIMIT:
435 $this->limit = null;
436 break;
437 case self::OFFSET:
438 $this->offset = null;
439 break;
440 case self::ORDER:
441 $this->order = array();
442 break;
443 case self::COMBINE:
444 $this->combine = array();
445 break;
447 return $this;
450 public function setSpecification($index, $specification)
452 if (!method_exists($this, 'process' . $index)) {
453 throw new Exception\InvalidArgumentException('Not a valid specification name.');
455 $this->specifications[$index] = $specification;
456 return $this;
459 public function getRawState($key = null)
461 $rawState = array(
462 self::TABLE => $this->table,
463 self::QUANTIFIER => $this->quantifier,
464 self::COLUMNS => $this->columns,
465 self::JOINS => $this->joins,
466 self::WHERE => $this->where,
467 self::ORDER => $this->order,
468 self::GROUP => $this->group,
469 self::HAVING => $this->having,
470 self::LIMIT => $this->limit,
471 self::OFFSET => $this->offset,
472 self::COMBINE => $this->combine
474 return (isset($key) && array_key_exists($key, $rawState)) ? $rawState[$key] : $rawState;
478 * Returns whether the table is read only or not.
480 * @return bool
482 public function isTableReadOnly()
484 return $this->tableReadOnly;
488 * Render table with alias in from/join parts
490 * @todo move TableIdentifier concatination here
491 * @param string $table
492 * @param string $alias
493 * @return string
495 protected function renderTable($table, $alias = null)
497 return $table . ($alias ? ' AS ' . $alias : '');
500 protected function processStatementStart(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
502 if ($this->combine !== array()) {
503 return array('(');
507 protected function processStatementEnd(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
509 if ($this->combine !== array()) {
510 return array(')');
515 * Process the select part
517 * @param PlatformInterface $platform
518 * @param DriverInterface $driver
519 * @param ParameterContainer $parameterContainer
520 * @return null|array
522 protected function processSelect(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
524 $expr = 1;
526 list($table, $fromTable) = $this->resolveTable($this->table, $platform, $driver, $parameterContainer);
527 // process table columns
528 $columns = array();
529 foreach ($this->columns as $columnIndexOrAs => $column) {
530 if ($column === self::SQL_STAR) {
531 $columns[] = array($fromTable . self::SQL_STAR);
532 continue;
535 $columnName = $this->resolveColumnValue(
536 array(
537 'column' => $column,
538 'fromTable' => $fromTable,
539 'isIdentifier' => true,
541 $platform,
542 $driver,
543 $parameterContainer,
544 (is_string($columnIndexOrAs) ? $columnIndexOrAs : 'column')
546 // process As portion
547 if (is_string($columnIndexOrAs)) {
548 $columnAs = $platform->quoteIdentifier($columnIndexOrAs);
549 } elseif (stripos($columnName, ' as ') === false) {
550 $columnAs = (is_string($column)) ? $platform->quoteIdentifier($column) : 'Expression' . $expr++;
552 $columns[] = (isset($columnAs)) ? array($columnName, $columnAs) : array($columnName);
555 // process join columns
556 foreach ($this->joins as $join) {
557 $joinName = (is_array($join['name'])) ? key($join['name']) : $join['name'];
558 $joinName = parent::resolveTable($joinName, $platform, $driver, $parameterContainer);
560 foreach ($join['columns'] as $jKey => $jColumn) {
561 $jColumns = array();
562 $jFromTable = is_scalar($jColumn)
563 ? $joinName . $platform->getIdentifierSeparator()
564 : '';
565 $jColumns[] = $this->resolveColumnValue(
566 array(
567 'column' => $jColumn,
568 'fromTable' => $jFromTable,
569 'isIdentifier' => true,
571 $platform,
572 $driver,
573 $parameterContainer,
574 (is_string($jKey) ? $jKey : 'column')
576 if (is_string($jKey)) {
577 $jColumns[] = $platform->quoteIdentifier($jKey);
578 } elseif ($jColumn !== self::SQL_STAR) {
579 $jColumns[] = $platform->quoteIdentifier($jColumn);
581 $columns[] = $jColumns;
585 if ($this->quantifier) {
586 $quantifier = ($this->quantifier instanceof ExpressionInterface)
587 ? $this->processExpression($this->quantifier, $platform, $driver, $parameterContainer, 'quantifier')
588 : $this->quantifier;
591 if (!isset($table)) {
592 return array($columns);
593 } elseif (isset($quantifier)) {
594 return array($quantifier, $columns, $table);
595 } else {
596 return array($columns, $table);
600 protected function processJoins(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
602 if (!$this->joins) {
603 return;
606 // process joins
607 $joinSpecArgArray = array();
608 foreach ($this->joins as $j => $join) {
609 $joinName = null;
610 $joinAs = null;
612 // table name
613 if (is_array($join['name'])) {
614 $joinName = current($join['name']);
615 $joinAs = $platform->quoteIdentifier(key($join['name']));
616 } else {
617 $joinName = $join['name'];
620 if ($joinName instanceof Expression) {
621 $joinName = $joinName->getExpression();
622 } elseif ($joinName instanceof TableIdentifier) {
623 $joinName = $joinName->getTableAndSchema();
624 $joinName = ($joinName[1] ? $platform->quoteIdentifier($joinName[1]) . $platform->getIdentifierSeparator() : '') . $platform->quoteIdentifier($joinName[0]);
625 } elseif ($joinName instanceof Select) {
626 $joinName = '(' . $this->processSubSelect($joinName, $platform, $driver, $parameterContainer) . ')';
627 } elseif (is_string($joinName) || (is_object($joinName) && is_callable(array($joinName, '__toString')))) {
628 $joinName = $platform->quoteIdentifier($joinName);
629 } else {
630 throw new Exception\InvalidArgumentException(sprintf('Join name expected to be Expression|TableIdentifier|Select|string, "%s" given', gettype($joinName)));
633 $joinSpecArgArray[$j] = array(
634 strtoupper($join['type']),
635 $this->renderTable($joinName, $joinAs),
638 // on expression
639 // note: for Expression objects, pass them to processExpression with a prefix specific to each join (used for named parameters)
640 $joinSpecArgArray[$j][] = ($join['on'] instanceof ExpressionInterface)
641 ? $this->processExpression($join['on'], $platform, $driver, $parameterContainer, 'join' . ($j+1) . 'part')
642 : $platform->quoteIdentifierInFragment($join['on'], array('=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>')); // on
645 return array($joinSpecArgArray);
648 protected function processWhere(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
650 if ($this->where->count() == 0) {
651 return;
653 return array(
654 $this->processExpression($this->where, $platform, $driver, $parameterContainer, 'where')
658 protected function processGroup(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
660 if ($this->group === null) {
661 return;
663 // process table columns
664 $groups = array();
665 foreach ($this->group as $column) {
666 $groups[] = $this->resolveColumnValue(
667 array(
668 'column' => $column,
669 'isIdentifier' => true,
671 $platform,
672 $driver,
673 $parameterContainer,
674 'group'
677 return array($groups);
680 protected function processHaving(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
682 if ($this->having->count() == 0) {
683 return;
685 return array(
686 $this->processExpression($this->having, $platform, $driver, $parameterContainer, 'having')
690 protected function processOrder(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
692 if (empty($this->order)) {
693 return;
695 $orders = array();
696 foreach ($this->order as $k => $v) {
697 if ($v instanceof ExpressionInterface) {
698 $orders[] = array(
699 $this->processExpression($v, $platform, $driver, $parameterContainer)
701 continue;
703 if (is_int($k)) {
704 if (strpos($v, ' ') !== false) {
705 list($k, $v) = preg_split('# #', $v, 2);
706 } else {
707 $k = $v;
708 $v = self::ORDER_ASCENDING;
711 if (strtoupper($v) == self::ORDER_DESCENDING) {
712 $orders[] = array($platform->quoteIdentifierInFragment($k), self::ORDER_DESCENDING);
713 } else {
714 $orders[] = array($platform->quoteIdentifierInFragment($k), self::ORDER_ASCENDING);
717 return array($orders);
720 protected function processLimit(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
722 if ($this->limit === null) {
723 return;
725 if ($parameterContainer) {
726 $parameterContainer->offsetSet('limit', $this->limit, ParameterContainer::TYPE_INTEGER);
727 return array($driver->formatParameterName('limit'));
729 return array($platform->quoteValue($this->limit));
732 protected function processOffset(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
734 if ($this->offset === null) {
735 return;
737 if ($parameterContainer) {
738 $parameterContainer->offsetSet('offset', $this->offset, ParameterContainer::TYPE_INTEGER);
739 return array($driver->formatParameterName('offset'));
742 return array($platform->quoteValue($this->offset));
745 protected function processCombine(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
747 if ($this->combine == array()) {
748 return;
751 $type = $this->combine['type'];
752 if ($this->combine['modifier']) {
753 $type .= ' ' . $this->combine['modifier'];
756 return array(
757 strtoupper($type),
758 $this->processSubSelect($this->combine['select'], $platform, $driver, $parameterContainer),
763 * Variable overloading
765 * @param string $name
766 * @throws Exception\InvalidArgumentException
767 * @return mixed
769 public function __get($name)
771 switch (strtolower($name)) {
772 case 'where':
773 return $this->where;
774 case 'having':
775 return $this->having;
776 default:
777 throw new Exception\InvalidArgumentException('Not a valid magic property for this object');
782 * __clone
784 * Resets the where object each time the Select is cloned.
786 * @return void
788 public function __clone()
790 $this->where = clone $this->where;
791 $this->having = clone $this->having;
795 * @param string|TableIdentifier|Select $table
796 * @param PlatformInterface $platform
797 * @param DriverInterface $driver
798 * @param ParameterContainer $parameterContainer
799 * @return string
801 protected function resolveTable($table, PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
803 $alias = null;
805 if (is_array($table)) {
806 $alias = key($table);
807 $table = current($table);
810 $table = parent::resolveTable($table, $platform, $driver, $parameterContainer);
812 if ($alias) {
813 $fromTable = $platform->quoteIdentifier($alias);
814 $table = $this->renderTable($table, $fromTable);
815 } else {
816 $fromTable = $table;
819 if ($this->prefixColumnsWithTable && $fromTable) {
820 $fromTable .= $platform->getIdentifierSeparator();
821 } else {
822 $fromTable = '';
825 return array(
826 $table,
827 $fromTable