mirror of
				https://github.com/Icinga/icingaweb2.git
				synced 2025-10-31 03:14:31 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			503 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			503 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| namespace Icinga\Data;
 | |
| 
 | |
| use Icinga\Application\Logger;
 | |
| use Icinga\Exception;
 | |
| use Icinga\Filter\Filterable;
 | |
| use Icinga\Filter\Query\Node;
 | |
| use Icinga\Filter\Query\Tree;
 | |
| use Zend_Paginator;
 | |
| use Icinga\Web\Paginator\Adapter\QueryAdapter;
 | |
| 
 | |
| abstract class BaseQuery implements Filterable
 | |
| {
 | |
|     /**
 | |
|      * Sort ascending
 | |
|      */
 | |
|     const SORT_ASC = 1;
 | |
| 
 | |
|     /**
 | |
|      * Sort descending
 | |
|      */
 | |
|     const SORT_DESC = -1;
 | |
| 
 | |
|     /**
 | |
|      * Query data source
 | |
|      *
 | |
|      * @var DatasourceInterface
 | |
|      */
 | |
|     protected $ds;
 | |
| 
 | |
|     /**
 | |
|      * The target of this query
 | |
|      * @var string
 | |
|      */
 | |
|     protected $table;
 | |
| 
 | |
|     /**
 | |
|      * The columns of the target that should be returned
 | |
|      * @var array
 | |
|      */
 | |
|     private $columns;
 | |
| 
 | |
|     /**
 | |
|      * The columns you're using to sort the query result
 | |
|      * @var array
 | |
|      */
 | |
|     private $orderColumns = array();
 | |
| 
 | |
|     /**
 | |
|      * Return not more than that many rows
 | |
|      * @var int
 | |
|      */
 | |
|     private $limitCount;
 | |
| 
 | |
|     /**
 | |
|      * Result starts with this row
 | |
|      * @var int
 | |
|      */
 | |
|     private $limitOffset;
 | |
| 
 | |
|     /**
 | |
|      * The backend independent filter to use for this query
 | |
|      *
 | |
|      * @var Tree
 | |
|      */
 | |
|     private $filter;
 | |
| 
 | |
|     /**
 | |
|      * Constructor
 | |
|      *
 | |
|      * @param DatasourceInterface $ds Your data source
 | |
|      */
 | |
|     public function __construct(DatasourceInterface $ds, array $columns = null)
 | |
|     {
 | |
|         $this->ds = $ds;
 | |
|         $this->columns = $columns;
 | |
|         $this->clearFilter();
 | |
|         $this->init();
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public function getDatasource()
 | |
|     {
 | |
|         return $this->ds;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     public function setColumns(array $columns)
 | |
|     {
 | |
|         $this->columns = $columns;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Define the target and attributes for this query
 | |
|      *
 | |
|      * The Query will return the default attribute the attributes parameter is omitted
 | |
|      *
 | |
|      * @param String $target    The target of this query (tablename, objectname, depends on the concrete implementation)
 | |
|      * @param array $columns   An optional array of columns to select, if none are given the default
 | |
|      *                          columnset is returned
 | |
|      *
 | |
|      * @return self             Fluent interface
 | |
|      */
 | |
|     public function from($target, array $attributes = null)
 | |
|     {
 | |
|         $this->table = $target;
 | |
|         if ($attributes !== null) {
 | |
|             $this->columns = $attributes;
 | |
|         }
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add a filter expression to be applied on this query.
 | |
|      *
 | |
|      * This is an alias to andWhere()
 | |
|      * The syntax of the expression and valid parameters are to be defined by the concrete
 | |
|      * backend-specific query implementation.
 | |
|      *
 | |
|      * @param string $expression    Implementation specific search expression
 | |
|      * @param mixed $parameters    Implementation specific search value to use for query placeholders
 | |
|      *
 | |
|      * @return self                 Fluent Interface
 | |
|      * @see BaseQuery::andWhere()   This is an alias to andWhere()
 | |
|      */
 | |
|     public function where($expression, $parameters = null)
 | |
|     {
 | |
|         return $this->andWhere($expression, $parameters);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add an mandatory filter expression to be applied on this query
 | |
|      *
 | |
|      * The syntax of the expression and valid parameters are to be defined by the concrete
 | |
|      * backend-specific query implementation.
 | |
|      *
 | |
|      * @param string $expression    Implementation specific search expression
 | |
|      * @param mixed $parameters    Implementation specific search value to use for query placeholders
 | |
|      * @return self                 Fluent interface
 | |
|      */
 | |
|     public function andWhere($expression, $parameters = null)
 | |
|     {
 | |
|         $node = $this->parseFilterExpression($expression, $parameters);
 | |
|         if ($node === null) {
 | |
|             Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
 | |
|             return $this;
 | |
|         }
 | |
|         $this->filter->insert(Node::createAndNode());
 | |
|         $this->filter->insert($node);
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add an lower priority filter expression to be applied on this query
 | |
|      *
 | |
|      * The syntax of the expression and valid parameters are to be defined by the concrete
 | |
|      * backend-specific query implementation.
 | |
|      *
 | |
|      * @param string $expression    Implementation specific search expression
 | |
|      * @param mixed $parameters    Implementation specific search value to use for query placeholders
 | |
|      * @return self                 Fluent interface
 | |
|      */
 | |
|     public function orWhere($expression, $parameters = null)
 | |
|     {
 | |
|         $node = $this->parseFilterExpression($expression, $parameters);
 | |
|         if ($node === null) {
 | |
|             Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
 | |
|             return $this;
 | |
|         }
 | |
|         $this->filter->insert(Node::createOrNode());
 | |
|         $this->filter->insert($node);
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determine whether the given field is a valid filter target
 | |
|      *
 | |
|      * The base implementation always returns true, overwrite it in concrete backend-specific
 | |
|      * implementations
 | |
|      *
 | |
|      * @param   String $field       The field to test for being filterable
 | |
|      * @return  bool                True if the field can be filtered, otherwise false
 | |
|      */
 | |
|     public function isValidFilterTarget($field)
 | |
|     {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return the internally used field name for the given alias
 | |
|      *
 | |
|      * The base implementation just returns the given field, overwrite it in concrete backend-specific
 | |
|      * implementations
 | |
|      *
 | |
|      * @param   String $field       The field to test for being filterable
 | |
|      * @return  bool                True if the field can be filtered, otherwise false
 | |
|      */
 | |
|     public function getMappedField($field)
 | |
|     {
 | |
|         return $field;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add a filter to this query
 | |
|      *
 | |
|      * This is the implementation for the Filterable, use where instead
 | |
|      *
 | |
|      * @param $filter
 | |
|      */
 | |
|     public function addFilter($filter)
 | |
|     {
 | |
|         if (is_string($filter)) {
 | |
|             $this->addFilter(call_user_func_array(array($this, 'parseFilterExpression'), func_get_args()));
 | |
|         } elseif ($filter instanceof Node) {
 | |
|             $this->filter->insert($filter);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function setFilter(Tree $filter)
 | |
|     {
 | |
|         $this->filter = $filter;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return all default columns
 | |
|      *
 | |
|      * @return array    An array of default columns to use when none are selected
 | |
|      */
 | |
|     public function getDefaultColumns()
 | |
|     {
 | |
|         return array();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Sort query result by the given column name
 | |
|      *
 | |
|      * Sort direction can be ascending (self::SORT_ASC, being the default)
 | |
|      * or descending (self::SORT_DESC).
 | |
|      *
 | |
|      * Preferred usage:
 | |
|      * <code>
 | |
|      * $query->sort('column_name ASC')
 | |
|      * </code>
 | |
|      *
 | |
|      * @param  string $columnOrAlias Column, may contain direction separated by space
 | |
|      * @param  int $dir Sort direction
 | |
|      *
 | |
|      * @return BaseQuery
 | |
|      */
 | |
|     public function order($columnOrAlias, $dir = null)
 | |
|     {
 | |
|         if ($dir === null) {
 | |
|             $colDirPair = explode(' ', $columnOrAlias, 2);
 | |
|             if (count($colDirPair) === 1) {
 | |
|                 $dir = $this->getDefaultSortDir($columnOrAlias);
 | |
|             } else {
 | |
|                 $dir = $colDirPair[1];
 | |
|                 $columnOrAlias = $colDirPair[0];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $dir = (strtoupper(trim($dir)) === 'DESC') ? self::SORT_DESC : self::SORT_ASC;
 | |
| 
 | |
|         $this->orderColumns[] = array($columnOrAlias, $dir);
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determine the default sort direction constant for the given column
 | |
|      *
 | |
|      * @param  String $col      The column to get the sort direction for
 | |
|      * @return int              Either SORT_ASC or SORT_DESC
 | |
|      */
 | |
|     protected function getDefaultSortDir($col)
 | |
|     {
 | |
|         return self::SORT_ASC;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Limit the result set
 | |
|      *
 | |
|      * @param int $count    The numeric maximum limit to apply on the query result
 | |
|      * @param int $offset   The offset to use for the result set
 | |
|      *
 | |
|      * @return BaseQuery
 | |
|      */
 | |
|     public function limit($count = null, $offset = null)
 | |
|     {
 | |
|         $this->limitCount = $count !== null ? intval($count) : null;
 | |
|         $this->limitOffset = intval($offset);
 | |
| 
 | |
|         return $this;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determine whether this query will be ordered explicitly
 | |
|      *
 | |
|      * @return bool     True when an order column has been set
 | |
|      */
 | |
|     public function hasOrder()
 | |
|     {
 | |
|         return !empty($this->orderColumns);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determine whether this query will be limited explicitly
 | |
|      *
 | |
|      * @return bool     True when an limit count has been set, otherwise false
 | |
|      */
 | |
|     public function hasLimit()
 | |
|     {
 | |
|         return $this->limitCount !== null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determine whether an offset is set or not
 | |
|      *
 | |
|      * @return bool     True when an offset > 0 is set
 | |
|      */
 | |
|     public function hasOffset()
 | |
|     {
 | |
|         return $this->limitOffset > 0;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the query limit
 | |
|      *
 | |
|      * @return int      The query limit or null if none is set
 | |
|      */
 | |
|     public function getLimit()
 | |
|     {
 | |
|         return $this->limitCount;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the query starting offset
 | |
|      *
 | |
|      * @return int      The query offset or null if none is set
 | |
|      */
 | |
|     public function getOffset()
 | |
|     {
 | |
|         return $this->limitOffset;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Implementation specific initialization
 | |
|      *
 | |
|      * Overwrite this instead of __construct (it's called at the end of the construct) to
 | |
|      * implement custom initialization logic on construction time
 | |
|      */
 | |
|     protected function init()
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return all columns set in this query or the default columns if none are set
 | |
|      *
 | |
|      * @return array    An array of columns
 | |
|      */
 | |
|     public function getColumns()
 | |
|     {
 | |
|         return ($this->columns !== null) ? $this->columns : $this->getDefaultColumns();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Return all columns used for ordering
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function getOrderColumns()
 | |
|     {
 | |
|         return $this->orderColumns;
 | |
|     }
 | |
| 
 | |
|     public function getFilter()
 | |
|     {
 | |
|         return $this->filter;
 | |
|     }
 | |
| 
 | |
|     public function clearFilter()
 | |
|     {
 | |
|         $this->filter = new Tree();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return a pagination adapter for this query
 | |
|      *
 | |
|      * @return \Zend_Paginator
 | |
|      */
 | |
|     public function paginate($limit = null, $page = null)
 | |
|     {
 | |
|         if ($page === null && $limit === null) {
 | |
|             $request = \Zend_Controller_Front::getInstance()->getRequest();
 | |
| 
 | |
|             if ($page === null) {
 | |
|                 $page = $request->getParam('page', 0);
 | |
|             }
 | |
| 
 | |
|             if ($limit === null) {
 | |
|                 $limit = $request->getParam('limit', 20);
 | |
|             }
 | |
|         }
 | |
|         $this->limit($limit, $page * $limit);
 | |
| 
 | |
|         $paginator = new Zend_Paginator(new QueryAdapter($this));
 | |
| 
 | |
|         $paginator->setItemCountPerPage($limit);
 | |
|         $paginator->setCurrentPageNumber($page);
 | |
| 
 | |
|         return $paginator;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Parse a backend specific filter expression and return a Query\Node object
 | |
|      *
 | |
|      * @param $expression       The expression to parse
 | |
|      * @param $parameters       Optional parameters for the expression
 | |
|      *
 | |
|      * @return Node             A query node or null if it's an invalid expression
 | |
|      */
 | |
|     protected function parseFilterExpression($expression, $parameter = null)
 | |
|     {
 | |
|         $splitted = explode(' ', $expression, 3);
 | |
|         if (count($splitted) === 1 && $parameter) {
 | |
|             return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
 | |
|         } elseif (count($splitted) === 2 && $parameter) {
 | |
|             Node::createOperatorNode($splitted[0], $splitted[1], is_string($parameter));
 | |
|             return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
 | |
|         } elseif (count($splitted) === 3) {
 | |
|             if (trim($splitted[2]) === '?' && is_string($parameter)) {
 | |
|                 return Node::createOperatorNode($splitted[1], $splitted[0], $parameter);
 | |
|             } else {
 | |
|                 return Node::createOperatorNode($splitted[1], $splitted[0], $splitted[2]);
 | |
|             }
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Total result size regardless of limit and offset
 | |
|      *
 | |
|      * @return int
 | |
|      */
 | |
|     public function count()
 | |
|     {
 | |
|         return $this->ds->count($this);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fetch result as an array of objects
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function fetchAll()
 | |
|     {
 | |
|         return $this->ds->fetchAll($this);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fetch first result row
 | |
|      *
 | |
|      * @return object
 | |
|      */
 | |
|     public function fetchRow()
 | |
|     {
 | |
|         return $this->ds->fetchRow($this);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fetch first result column
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function fetchColumn()
 | |
|     {
 | |
|         return $this->ds->fetchColumn($this);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fetch first column value from first result row
 | |
|      *
 | |
|      * @return mixed
 | |
|      */
 | |
|     public function fetchOne()
 | |
|     {
 | |
|         return $this->ds->fetchOne($this);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fetch result as a key/value pair array
 | |
|      *
 | |
|      * @return array
 | |
|      */
 | |
|     public function fetchPairs()
 | |
|     {
 | |
|         return $this->ds->fetchPairs($this);
 | |
|     }
 | |
| }
 |