lib/Ajde/Collection.php

Summary

Maintainability
D
3 days
Test Coverage
<?php

class Ajde_Collection extends Ajde_Object_Standard implements Iterator, Countable
{
    /**
     * @var string
     */
    protected $_modelName;

    /**
     * @var PDO
     */
    protected $_connection;

    /**
     * @var PDOStatement
     */
    protected $_statement;

    /**
     * @var Ajde_Query
     */
    protected $_query;

    protected $_link = [];

    /**
     * @var Ajde_Db_Table
     */
    protected $_table;

    protected $_filters = [];
    public $_filterValues = [];

    /**
     * @var Ajde_Collection_View
     */
    protected $_view;

    // For Iterator
    protected $_items = null;
    protected $_position = 0;

    private $_sqlInitialized = false;
    private $_queryCount;

    public static function extendController(Ajde_Controller $controller, $method, $arguments)
    {
        // Register getCollection($name) function on Ajde_Controller
        if ($method === 'getCollection') {
            return self::getCollection($arguments[0]);
        }
        // TODO: if last triggered in event cueue, throw exception
        // throw new Ajde_Exception("Call to undefined method ".get_class($controller)."::$method()", 90006);
        // Now, we give other callbacks in event cueue chance to return
    }

    public static function getCollection($name)
    {
        $collectionName = ucfirst($name).'Collection';

        return new $collectionName();
    }

    public function __construct()
    {
        $this->_modelName = str_replace('Collection', '', get_class($this)).'Model';
        $this->_connection = Ajde_Db::getInstance()->getConnection();

        $tableNameCC = str_replace('Collection', '', get_class($this));
        $tableName = $this->fromCamelCase($tableNameCC);

        $this->_table = Ajde_Db::getInstance()->getTable($tableName);
        $this->_query = new Ajde_Query();
    }

    public function reset()
    {
        parent::reset();
        $this->_query = new Ajde_Query();
        $this->_filters = [];
        $this->_filterValues = [];
        $this->_items = null;
        $this->_position = 0;
        $this->_queryCount = null;
        $this->_sqlInitialized = false;
    }

    public function __sleep()
    {
        return ['_modelName', '_items'];
    }

    public function __wakeup()
    {
    }

    public function rewind()
    {
        if (!isset($this->_items)) {
            $this->load();
        }
        $this->_position = 0;
    }

    public function current()
    {
        return $this->_items[$this->_position];
    }

    public function key()
    {
        return $this->_position;
    }

    public function next()
    {
        $this->_position++;
    }

    public function count($query = false)
    {
        if ($query == true) {
            if (!isset($this->_queryCount)) {
                $this->_statement = $this->getConnection()->prepare($this->getCountSql());
                foreach ($this->getFilterValues() as $key => $value) {
                    if (is_null($value)) {
                        $this->_statement->bindValue(":$key", null, PDO::PARAM_NULL);
                    } else {
                        $this->_statement->bindValue(":$key", $value, PDO::PARAM_STR);
                    }
                }
                $this->_statement->execute();
                $result = $this->_statement->fetch(PDO::FETCH_ASSOC);
                $this->_queryCount = $result['count'];
            }

            return $this->_queryCount;
        } else {
            if (!isset($this->_items)) {
                $this->load();
            }

            return count($this->_items);
        }
    }

    /**
     * @param string $field
     * @param mixed  $value
     *
     * @return Ajde_Model | boolean
     */
    public function find($field, $value)
    {
        foreach ($this as $item) {
            if ($item->{$field} == $value) {
                return $item;
            }
        }

        return false;
    }

    public function valid()
    {
        return isset($this->_items[$this->_position]);
    }

    /**
     * @return Ajde_Db_PDO
     */
    public function getConnection()
    {
        return $this->_connection;
    }

    /**
     * @return Ajde_Db_Table
     */
    public function getTable()
    {
        return $this->_table;
    }

    /**
     * @return PDOStatement
     */
    public function getStatement()
    {
        return $this->_statement;
    }

    /**
     * @return Ajde_Query
     */
    public function getQuery()
    {
        return $this->_query;
    }

    public function populate($array)
    {
        $this->reset();
        $this->_data = $array;
    }

    public function getLink($modelName, $value)
    {
        if (!array_key_exists($modelName, $this->_link)) {
            // TODO:
            throw new Ajde_Exception('Link not defined...');
        }

        return new Ajde_Filter_Link($this, $modelName, $this->_link[$modelName], $value);
    }

    // Chainable collection methods

    public function addFilter(Ajde_Filter $filter)
    {
        $this->_filters[] = $filter;

        return $this;
    }

    public function orderBy($field, $direction = Ajde_Query::ORDER_ASC)
    {
        $this->getQuery()->addOrderBy($field, $direction);

        return $this;
    }

    public function limit($count, $start = 0)
    {
        $this->getQuery()->limit((int) $count, (int) $start);

        return $this;
    }

    public function filter($field, $value, $comparison = Ajde_Filter::FILTER_EQUALS, $operator = Ajde_Query::OP_AND)
    {
        $this->addFilter(new Ajde_Filter_Where($field, $comparison, $value, $operator));

        return $this;
    }

    // View functions

    public function setView(Ajde_Collection_View $view)
    {
        $this->_view = $view;
    }

    /**
     * @return Ajde_Collection_View
     */
    public function getView()
    {
        return $this->_view;
    }

    /**
     * @return bool
     */
    public function hasView()
    {
        return isset($this->_view) && $this->_view instanceof Ajde_Collection_View;
    }

    public function applyView(Ajde_Collection_View $view = null)
    {
        if (!$this->hasView() && !isset($view)) {
            // TODO:
            throw new Ajde_Exception('No view set');
        }

        if (isset($view)) {
            $this->setView($view);
        } else {
            $view = $this->getView();
        }

        // LIMIT
        $this->limit($view->getPageSize(), $view->getRowStart());

        // ORDER BY
        if (!$view->isEmpty('orderBy')) {
            $oldOrderBy = $this->getQuery()->orderBy;
            $this->getQuery()->orderBy = [];
            if (in_array($view->getOrderBy(), $this->getTable()->getFieldNames())) {
                $this->orderBy((string) $this->getTable().'.'.$view->getOrderBy(), $view->getOrderDir());
            } else {
                // custom column, make sure to add it to the query first!
                $this->orderBy($view->getOrderBy(), $view->getOrderDir());
            }
            foreach ($oldOrderBy as $orderBy) {
                $this->orderBy($orderBy['field'], $orderBy['direction']);
            }
        }

        // FILTER
        if (!$view->isEmpty('filter')) {
            foreach ($view->getFilter() as $fieldName => $filterValue) {
                if ($filterValue != '') {
                    $fieldType = $this->getTable()->getFieldProperties($fieldName, 'type');
                    if ($fieldType == Ajde_Db::FIELD_TYPE_DATE) {
                        // date fields
                        $start = $filterValue['start'] ? date('Y-m-d H:i:s',
                            strtotime($filterValue['start'].' 00:00:00')) : false;
                        $end = $filterValue['end'] ? date('Y-m-d H:i:s',
                            strtotime($filterValue['end'].' 23:59:59')) : false;
                        if ($start) {
                            $this->addFilter(new Ajde_Filter_Where((string) $this->getTable().'.'.$fieldName,
                                Ajde_Filter::FILTER_GREATEROREQUAL, $start));
                        }
                        if ($end) {
                            $this->addFilter(new Ajde_Filter_Where((string) $this->getTable().'.'.$fieldName,
                                Ajde_Filter::FILTER_LESSOREQUAL, $end));
                        }
                    } else {
                        if ($fieldType == Ajde_Db::FIELD_TYPE_TEXT) {
                            // text fields (fuzzy)
                            $this->addFilter(new Ajde_Filter_Where((string) $this->getTable().'.'.$fieldName,
                                Ajde_Filter::FILTER_LIKE, '%'.$filterValue.'%'));
                        } else {
                            // non-date fields (exact match)
                            $this->addFilter(new Ajde_Filter_Where((string) $this->getTable().'.'.$fieldName,
                                Ajde_Filter::FILTER_EQUALS, $filterValue));
                        }
                    }
                }
            }
        }

        // SEARCH
        if (!$view->isEmpty('search')) {
            $this->addTextFilter($view->getSearch());
        }
    }

    public function addTextFilter($text, $operator = Ajde_Query::OP_AND, $condition = Ajde_Filter::CONDITION_WHERE)
    {
        $searchFilter = $this->getTextFilterGroup($text, $operator, $condition);
        if ($searchFilter !== false) {
            $this->addFilter($searchFilter);
        } else {
            $this->addFilter(new Ajde_Filter_Where('true', '=', 'false'));
        }
    }

    public function getTextFilterGroup($text, $operator = Ajde_Query::OP_AND, $condition = Ajde_Filter::CONDITION_WHERE)
    {
        $groupClass = 'Ajde_Filter_'.ucfirst($condition).'Group';
        $filterClass = 'Ajde_Filter_'.ucfirst($condition);

        $searchFilter = new $groupClass($operator);
        $fieldOptions = $this->getTable()->getFieldProperties();
        foreach ($fieldOptions as $fieldName => $fieldProperties) {
            switch ($fieldProperties['type']) {
                case Ajde_Db::FIELD_TYPE_TEXT:
                case Ajde_Db::FIELD_TYPE_ENUM:
                    $searchFilter->addFilter(new $filterClass((string) $this->getTable().'.'.$fieldName,
                        Ajde_Filter::FILTER_LIKE, '%'.$text.'%', Ajde_Query::OP_OR));
                    break;
                case Ajde_Db::FIELD_TYPE_NUMERIC:
                    $searchFilter->addFilter(new $filterClass('CAST('.(string) $this->getTable().'.'.$fieldName.' AS CHAR)',
                        Ajde_Filter::FILTER_LIKE, '%'.$text.'%', Ajde_Query::OP_OR));
                    break;
                default:
                    break;
            }
        }

        return $searchFilter->hasFilters() ? $searchFilter : false;
    }

    public function getSql()
    {
        if (!$this->_sqlInitialized) {
            foreach ($this->getTable()->getFieldNames() as $field) {
                $this->getQuery()->addSelect((string) $this->getTable().'.'.$field);
            }
            if (!empty($this->_filters)) {
                foreach ($this->getFilter('select') as $select) {
                    call_user_func_array([$this->getQuery(), 'addSelect'], $select);
                }
            }
            $this->getQuery()->addFrom($this->_table);
            if (!empty($this->_filters)) {
                foreach ($this->getFilter('join') as $join) {
                    call_user_func_array([$this->getQuery(), 'addJoin'], $join);
                }
                foreach ($this->getFilter('where') as $where) {
                    call_user_func_array([$this->getQuery(), 'addWhere'], $where);
                }
                foreach ($this->getFilter('having') as $having) {
                    call_user_func_array([$this->getQuery(), 'addHaving'], $having);
                }
            }
        }
        $this->_sqlInitialized = true;

        return $this->getQuery()->getSql();
    }

    public function getCountSql()
    {
        // Make sure to load the filters
        $this->getSql();
        $query = clone $this->getQuery();
        /* @var $query Ajde_Query */
        $query->select = [];
        $query->orderBy = [];
        $query->limit = ['start' => null, 'count' => null];
        $query->addSelect('COUNT(*) AS count');

        return $query->getSql();
    }

    public function getEmulatedSql()
    {
        return Ajde_Db_PDOStatement::getEmulatedSql($this->getSql(), $this->getFilterValues());
    }

    public function getFilter($queryPart)
    {
        $arguments = [];
        foreach ($this->_filters as $filter) {
            $prepared = $filter->prepare($this->getTable());
            if (isset($prepared[$queryPart])) {
                if (isset($prepared[$queryPart]['values'])) {
                    $this->_filterValues = array_merge($this->_filterValues, $prepared[$queryPart]['values']);
                }
                $arguments[] = $prepared[$queryPart]['arguments'];
            }
        }
        if (empty($arguments)) {
            return [];
        } else {
            return $arguments;
        }
    }

    public function getFilterValues()
    {
        return $this->_filterValues;
    }

    // Load the collection
    public function load()
    {
        if (!$this->getConnection() instanceof Ajde_Db_PDO) {
            // return false;
        }
        $this->_statement = $this->getConnection()->prepare($this->getSql());
        foreach ($this->getFilterValues() as $key => $value) {
            if (is_null($value)) {
                $this->_statement->bindValue(":$key", null, PDO::PARAM_NULL);
            } else {
                $this->_statement->bindValue(":$key", $value, PDO::PARAM_STR);
            }
        }
        $this->_statement->execute();

        return $this->_items = $this->_statement->fetchAll(PDO::FETCH_CLASS, $this->_modelName);
    }

    public function first()
    {
        $this->limit(1);
        $items = $this->load();

        if (count($items)) {
            return $items[0];
        }
    }

    public function loadParents()
    {
        if (count($this) > 0) {
            foreach ($this as $model) {
                $model->loadParents();
            }
        }
    }

    public function length()
    {
        if (!isset($this->_items)) {
            $this->load();
        }

        return count($this->_items);
    }

    public function hash()
    {
        $str = '';
        /** @var $item Ajde_Model */
        foreach ($this as $item) {
            $str .= implode('', $item->valuesAsSingleDimensionArray());
        }

        return md5($str);
    }

    public function toArray()
    {
        $array = [];
        foreach ($this as $item) {
            $array[] = $item->values();
        }

        return $array;
    }

    public function items()
    {
        if (!isset($this->_items)) {
            $this->load();
        }

        return $this->_items;
    }

    public function add($item)
    {
        $this->_items[] = $item;
    }

    public function combine(Ajde_Collection $collection)
    {
        foreach ($collection as $item) {
            $this->add($item);
        }

        return $this;
    }

    public function deleteAll()
    {
        foreach ($this as $item) {
            $item->delete();
        }
    }
}