/solar/source/solar/Solar/Sql/Model.php
PHP | 2664 lines | 1023 code | 266 blank | 1375 comment | 145 complexity | ba94ff6e5dd45cce2a55ffdade4dcc48 MD5 | raw file
Possible License(s): MIT
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- *
- * An SQL-centric Model class based on TableDataGateway, using Collection and
- * Record objects for returns, with integrated caching of versioned result
- * data.
- *
- * @category Solar
- *
- * @package Solar_Sql_Model An SQL-oriented ORM system using TableDataGateway
- * and DataMapper patterns.
- *
- * @author Paul M. Jones <pmjones@solarphp.com>
- *
- * @author Jeff Moore <jeff@procata.com>
- *
- * @license http://opensource.org/licenses/bsd-license.php BSD
- *
- * @version $Id: Model.php 4694 2010-09-06 14:33:18Z pmjones $
- *
- */
- abstract class Solar_Sql_Model extends Solar_Base
- {
- /**
- *
- * Default configuration values.
- *
- * @config dependency sql A Solar_Sql dependency.
- *
- * @config dependency cache A Solar_Cache dependency for the
- * Solar_Sql_Model_Cache object.
- *
- * @config dependency catalog A Solar_Sql_Model_Catalog to find other
- * models with.
- *
- * @config bool table_scan Connect to the database and scan the table for
- * its column descriptions, creating the table and indexes if not already
- * present.
- *
- * @config bool auto_cache Automatically maintain the data cache.
- *
- * @var array
- *
- */
- protected $_Solar_Sql_Model = array(
- 'catalog' => 'model_catalog',
- 'sql' => 'sql',
- 'cache' => array(
- 'adapter' => 'Solar_Cache_Adapter_None',
- ),
- 'table_scan' => true,
- 'auto_cache' => false,
- );
-
- /**
- *
- * The number of rows affected by the last INSERT, UPDATE, or DELETE.
- *
- * @var int
- *
- * @see getAffectedRows()
- *
- */
- protected $_affected_rows;
-
- /**
- *
- * A Solar_Sql_Model_Catalog dependency object.
- *
- * @var Solar_Sql_Model_Catalog
- *
- */
- protected $_catalog = null;
-
- /**
- *
- * A Solar_Sql dependency object.
- *
- * @var Solar_Sql_Adapter
- *
- */
- protected $_sql = null;
-
- /**
- *
- * A Solar_Sql_Model_Cache object.
- *
- * @var Solar_Sql_Model_Cache
- *
- */
- protected $_cache = null;
-
- /**
- *
- * The model name is the short form of the class name; this is generally
- * a plural.
- *
- * When inheritance is enabled, the default is the $_inherit_name value,
- * otherwise, the default is the $_table_name.
- *
- * @var string
- *
- */
- protected $_model_name;
-
- /**
- *
- * When a record from this model is part of an form element array, use
- * this name as the array key for it; by default, this is the singular
- * of the model name.
- *
- * @var string
- *
- */
- protected $_array_name;
-
- // -----------------------------------------------------------------
- //
- // Classes
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * A Solar_Class_Stack object for fallback hierarchy.
- *
- * @var Solar_Class_Stack
- *
- */
- protected $_stack;
-
- /**
- *
- * The results of get_class($this) so we don't call get_class() all the
- * time.
- *
- * @var string
- *
- */
- protected $_class;
-
- /**
- *
- * The final fallback class for an individual record.
- *
- * Default is Solar_Sql_Model_Record.
- *
- * @var string
- *
- */
- protected $_record_class = 'Solar_Sql_Model_Record';
-
- /**
- *
- * A blank instance of the Record class for this model.
- *
- * We keep this so we don't keep looking for a record class once we know
- * what the proper class is. Not used when inheritance is in effect.
- *
- * @var Solar_Sql_Model_Record
- *
- */
- protected $_record_prototype;
-
- /**
- *
- * The final fallback class for collections of records.
- *
- * Default is Solar_Sql_Model_Collection.
- *
- * @var string
- *
- */
- protected $_collection_class = 'Solar_Sql_Model_Collection';
-
- /**
- *
- * A blank instance of the Collection class for this model.
- *
- * We keep this so we don't keep looking for a collection class once we
- * know what the proper class is.
- *
- * @var Solar_Sql_Model_Record
- *
- */
- protected $_collection_prototype;
-
- /**
- *
- * The class to use for building SELECT statements.
- *
- * @var string
- *
- */
- protected $_select_class = 'Solar_Sql_Select';
-
- /**
- *
- * The class to use for filter chains.
- *
- * @var string
- *
- */
- protected $_filter_class = null;
-
- /**
- *
- * The class to use for the cache object.
- *
- * @var string
- *
- * @see $_cache
- *
- */
- protected $_cache_class = 'Solar_Sql_Model_Cache';
-
- // -----------------------------------------------------------------
- //
- // Table and index definition
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * The table name.
- *
- * @var string
- *
- */
- protected $_table_name = null;
-
- /**
- *
- * The column specification array for all columns in this table.
- *
- * Used in auto-creation, and for sync-checks.
- *
- * Will be overridden by _fixTableCols() when it reads the table info, so
- * you don't *have* to enter anything here ... but if it's empty, you
- * won't get auto-creation.
- *
- * Each element in this array looks like this...
- *
- * {{code: php
- * $_table_cols = array(
- * 'col_name' => array(
- * 'name' => (string) the col_name, same as the key
- * 'type' => (string) char, varchar, date, etc
- * 'size' => (int) column size
- * 'scope' => (int) decimal places
- * 'default' => (string) default value
- * 'require' => (bool) is this a required (non-null) column?
- * 'primary' => (bool) is this part of the primary key?
- * 'autoinc' => (bool) auto-incremented?
- * ),
- * );
- * }}
- *
- * @var array
- *
- */
- protected $_table_cols = array();
-
- /**
- *
- * The index specification array for all indexes on this table.
- *
- * Used only in auto-creation.
- *
- * The array should be in this format ...
- *
- * {{code: php
- * // the index type: 'normal' or 'unique'
- * $type = 'normal';
- *
- * // index on a single column:
- * // CREATE INDEX idx_name ON table_name (col_name)
- * $this->_index_info['idx_name'] = array(
- * 'type' => $type,
- * 'cols' => 'col_name'
- * );
- *
- * // index on multiple columns:
- * // CREATE INDEX idx_name ON table_name (col_1, col_2, ... col_N)
- * $this->_index_info['idx_name'] = array(
- * 'type' => $type,
- * 'cols' => array('col_1', 'col_2', ..., 'col_N')
- * );
- *
- * // easy shorthand for an index on a single column,
- * // giving the index the same name as the column:
- * // CREATE INDEX col_name ON table_name (col_name)
- * $this->_index_info['col_name'] = $type;
- * }}
- *
- * The $type may be 'normal' or 'unique'.
- *
- * @var array
- *
- */
- protected $_index_info = array();
-
- // -----------------------------------------------------------------
- //
- // Special columns and column behaviors
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * A list of column names that don't exist in the table, but should be
- * calculated by the model as-needed.
- *
- * @var array
- *
- */
- protected $_calculate_cols = array();
-
- /**
- *
- * A list of column names that use sequence values.
- *
- * When the column is present in a data array, but its value is null,
- * a sequence value will automatically be added.
- *
- * @var array
- *
- */
- protected $_sequence_cols = array();
-
- /**
- *
- * A list of column names on which to apply serialize() and unserialize()
- * automatically.
- *
- * Will be unserialized by the Record class as the values are loaded,
- * then re-serialized just before insert/update in the Model class.
- *
- * @var array
- *
- * @see [[php::serialize() | ]]
- *
- * @see [[php::unserialize() | ]]
- *
- */
- protected $_serialize_cols = array();
-
- /**
- *
- * A list of column names storing XML strings to convert back and forth to
- * Solar_Struct_Xml objects.
- *
- * @var array
- *
- * @see $_xmlstruct_class
- *
- * @see Solar_Struct_Xml
- *
- */
- protected $_xmlstruct_cols = array();
-
- /**
- *
- * The class to use for $_xmlstruct_cols conversion objects.
- *
- * @var string
- *
- * @var array
- *
- * @see $_xmlstruct_cols
- *
- */
- protected $_xmlstruct_class = 'Solar_Struct_Xml';
-
- /**
- *
- * The column name for the primary key.
- *
- * @var string
- *
- */
- protected $_primary_col = null;
-
- /**
- *
- * The column name for 'created' timestamps; default is 'created'.
- *
- * @var string
- *
- */
- protected $_created_col = 'created';
-
- /**
- *
- * The column name for 'updated' timestamps; default is 'updated'.
- *
- * @var string
- *
- */
- protected $_updated_col = 'updated';
-
- /**
- *
- * Other models that relate to this model should use this as the
- * foreign-key column name.
- *
- * @var string
- *
- */
- protected $_foreign_col = null;
-
- // -----------------------------------------------------------------
- //
- // Other/misc
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Relationships to other Model classes.
- *
- * Keyed on a "virtual" column name, which will be used as a property
- * name in returned records.
- *
- * @var array
- *
- */
- protected $_related = array();
-
- /**
- *
- * Filters to validate and sanitize column data.
- *
- * Default is to use validate*() and sanitize*() methods in the filter
- * class, but if the method exists locally, it will be used instead.
- *
- * The filters apply only to Record objects from the model; if you use
- * the model insert() and update() methods directly, the filters are not
- * applied.
- *
- * Example usage follows; note that "_validate" and "_sanitize" refer
- * to internal (protected) filtering methods that have access to the
- * entire data set being filtered.
- *
- * {{code: php
- * // filter 'col_1' to have only alpha chars, with a max length of
- * // 32 chars
- * $this->_filters['col_1'][] = 'sanitizeStringAlpha';
- * $this->_filters['col_1'][] = array('validateMaxLength', 32);
- *
- * // filter 'col_2' to have only numeric chars, validate as an
- * // integer, in a range of -10 to +10.
- * $this->_filters['col_2'][] = 'sanitizeNumeric';
- * $this->_filters['col_2'][] = 'validateInteger';
- * $this->_filters['col_2'][] = array('validateRange', -10, +10);
- *
- * // filter 'handle' to have only alpha-numeric chars, with a length
- * // of 6-14 chars, and unique in the table.
- * $this->_filters['handle'][] = 'sanitizeStringAlnum';
- * $this->_filters['handle'][] = array('validateRangeLength', 6, 14);
- * $this->_filters['handle'][] = 'validateUnique';
- *
- * // filter 'email' to have only emails-allowed chars, validate as an
- * // email address, and be unique in the table.
- * $this->_filters['email'][] = 'sanitizeStringEmail';
- * $this->_filters['email'][] = 'validateEmail';
- * $this->_filters['email'][] = 'validateUnique';
- *
- * // filter 'passwd' to be not-blank, and should match any existing
- * // 'passwd_confirm' value.
- * $this->_filters['passwd'][] = 'validateNotBlank';
- * $this->_filters['passwd'][] = 'validateConfirm';
- * }}
- *
- * @var array
- *
- * @see $_filter_class
- *
- * @see _addFilter()
- *
- */
- protected $_filters;
-
- // -----------------------------------------------------------------
- //
- // Single-table inheritance
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * The base model this class is inherited from, in single-table
- * inheritance.
- *
- * @var string
- *
- */
- protected $_inherit_base = null;
-
- /**
- *
- * When inheritance is turned on, the class name value for this class
- * in $_inherit_col.
- *
- * @var string
- *
- */
- protected $_inherit_name = false;
-
- /**
- *
- * The column name that tracks single-table inheritance; default is
- * 'inherit'.
- *
- * @var string
- *
- */
- protected $_inherit_col = 'inherit';
-
- // -----------------------------------------------------------------
- //
- // Select options
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Only fetch these columns from the table.
- *
- * @var array
- *
- */
- protected $_fetch_cols;
-
- /**
- *
- * By default, order by this column when fetching rows.
- *
- * @var array
- *
- */
- protected $_order;
-
- /**
- *
- * The default number of rows per page when selecting.
- *
- * @var int
- *
- */
- protected $_paging = 10;
-
- /**
- *
- * The registered Solar_Inflect object.
- *
- * @var Solar_Inflect
- *
- */
- protected $_inflect;
-
- // -----------------------------------------------------------------
- //
- // Constructor and magic methods
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Post-construction tasks to complete object construction.
- *
- * @return void
- *
- */
- protected function _postConstruct()
- {
- parent::_postConstruct();
-
- // Establish the state of this object before _setup
- $this->_preSetup();
-
- // user-defined setup
- $this->_setup();
-
- // Complete the setup of this model
- $this->_postSetup();
- }
-
- /**
- *
- * Call this before you unset the instance so that you release the memory
- * from all the internal child objects.
- *
- * @return void
- *
- */
- public function free()
- {
- foreach ($this->_related as $key => $val) {
- unset($this->_related[$key]);
- }
-
- unset($this->_cache);
- }
-
- /**
- *
- * User-defined setup.
- *
- * @return void
- *
- */
- protected function _setup()
- {
- }
-
- // -----------------------------------------------------------------
- //
- // Getters and setters
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Read-only access to protected model properties.
- *
- * @param string $key The requested property; e.g., `'foo'` will read from
- * `$_foo`.
- *
- * @return mixed
- *
- */
- public function __get($key)
- {
- $var = "_$key";
- if (property_exists($this, $var)) {
- return $this->$var;
- } else {
- throw $this->_exception('ERR_NO_SUCH_PROPERTY', array(
- 'class' => get_class($this),
- 'property' => $key,
- ));
- }
- }
-
- /**
- *
- * Gets the number of records per page.
- *
- * @return int The number of records per page.
- *
- */
- public function getPaging()
- {
- return $this->_paging;
- }
-
- /**
- *
- * Sets the number of records per page.
- *
- * @param int $paging The number of records per page.
- *
- * @return void
- *
- */
- public function setPaging($paging)
- {
- $this->_paging = (int) $paging;
- }
-
- /**
- *
- * Returns the fully-qualified primary key name.
- *
- * @return string
- *
- */
- public function getPrimary()
- {
- return "{$this->_model_name}.{$this->_primary_col}";
- }
-
- /**
- *
- * Returns the number of rows affected by the last INSERT, UPDATE, or
- * DELETE.
- *
- * @return int
- *
- */
- public function getAffectedRows()
- {
- return $this->_affected_rows;
- }
-
- // -----------------------------------------------------------------
- //
- // Fetch
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Magic call implements "fetchOneBy...()" and "fetchAllBy...()" for
- * columns listed in the method name.
- *
- * You *have* to specify params for all of the named columns.
- *
- * Optionally, you can pass a final array for the "extra" paramters to the
- * fetch ('order', 'group', 'having', etc.)
- *
- * Example:
- *
- * {{code: php
- * // fetches one record by status
- * $model->fetchOneByStatus('draft');
- *
- * // fetches all records by area_id and owner_handle
- * $model->fetchAllByAreaIdAndOwnerHandle($area_id, $owner_handle);
- *
- * // fetches all records by area_id and owner_handle,
- * // with ordering and page-limiting
- * $extra = array('order' => 'area_id DESC', 'page' => 2);
- * $model->fetchAllByAreaIdAndOwnerHandle($area_id, $owner_handle, $extra);
- * }}
- *
- * @param string $method The virtual method name, composed of "fetchOneBy"
- * or "fetchAllBy", with a series of column names joined by "And".
- *
- * @param array $params Parameters to pass to the method: one for each
- * column, plus an optional one for extra fetch parameters.
- *
- * @return mixed
- *
- * @todo Expand to cover assoc, col, pairs, and value.
- *
- */
- public function __call($method, $params)
- {
- // fetch a record, or a collection?
- if (substr($method, 0, 7) == 'fetchBy') {
- // fetch a record
- $fetch = 'fetchOne';
- $method = substr($method, 7);
- } elseif (substr($method, 0, 10) == 'fetchOneBy') {
- // fetch a record
- $fetch = 'fetchOne';
- $method = substr($method, 10);
- } elseif (substr($method, 0, 10) == 'fetchAllBy') {
- // fetch a collection
- $fetch = 'fetchAll';
- $method = substr($method, 10);
- } else {
- throw $this->_exception('ERR_METHOD_NOT_IMPLEMENTED', array(
- 'method' => $method,
- ));
- }
-
- // get the list of columns from the remainder of the method name
- // e.g., fetchAllByParentIdAndAreaId => ParentId, AreaId
- $list = explode('And', $method);
-
- // build the fetch params
- $where = array();
- foreach ($list as $key => $col) {
- // convert from ColName to col_name
- $col = strtolower(
- $this->_inflect->camelToUnder($col)
- );
- $where["{$this->_model_name}.$col = ?"] = $params[$key];
- }
-
- // add the last param after last column name as the "extra" settings
- // (order, group, having, page, paging, etc).
- $k = count($list);
- if (count($params) > $k) {
- $opts = (array) $params[$k];
- } else {
- $opts = array();
- }
-
- // merge the where with the base fetch params
- $opts = array_merge($opts, array(
- 'where' => $where,
- ));
-
- // do the fetch
- return $this->$fetch($opts);
- }
-
- /**
- *
- * Fetches a record or collection by primary key value(s).
- *
- * @param int|array $spec The primary key value for a single record, or an
- * array of primary key values for a collection of records.
- *
- * @param array $fetch An array of parameters for the fetch, with keys
- * for 'cols', 'group', 'having', 'order', etc. Note that the 'where'
- * and 'order' elements are overridden and have no effect.
- *
- * @return Solar_Sql_Model_Record|Solar_Sql_Model_Collection A record or
- * record-set object.
- *
- */
- public function fetch($spec, $fetch = null)
- {
- $col = "{$this->_model_name}.{$this->_primary_col}";
- if (is_array($spec)) {
- $fetch['where'] = array("$col IN (?)" => $spec);
- $fetch['order'] = $col;
- return $this->fetchAll($fetch);
- } else {
- $fetch['where'] = array("$col = ?" => $spec);
- $fetch['order'] = $col;
- return $this->fetchOne($fetch);
- }
- }
-
- /**
- *
- * Fetches a collection of all records by arbitrary parameters.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return Solar_Sql_Model_Collection A collection object.
- *
- */
- public function fetchAll($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('all', $fetch);
- if (! $result) {
- return array();
- }
-
- // create a collection from the result
- $coll = $this->newCollection($result);
-
- // add pager-info to the collection
- if ($fetch['count_pages']) {
- $this->_setCollectionPagerInfo($coll, $fetch);
- }
-
- // done
- return $coll;
- }
-
- /**
- *
- * Fetches an array of rows by arbitrary parameters.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return array
- *
- */
- public function fetchAllAsArray($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('all', $fetch);
- if (! $result) {
- return array();
- } else {
- return $result;
- }
- }
-
- /**
- *
- * The same as fetchAll(), except the record collection is keyed on the
- * first column of the results (instead of being a strictly sequential
- * array.)
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return Solar_Sql_Model_Collection A collection object.
- *
- */
- public function fetchAssoc($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('assoc', $fetch);
- if (! $result) {
- return array();
- }
-
- // create a collection from the result
- $coll = $this->newCollection($result);
-
- // add pager-info to the collection
- if ($fetch['count_pages']) {
- $this->_setCollectionPagerInfo($coll, $fetch);
- }
-
- // done
- return $coll;
- }
-
- /**
- *
- * The same as fetchAssoc(), except it returns an array, not a collection.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return array An array of rows.
- *
- */
- public function fetchAssocAsArray($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('assoc', $fetch);
- if (! $result) {
- return array();
- } else {
- return $result;
- }
- }
-
- /**
- *
- * Sets the pager info in a collection, calling countPages() along the
- * way.
- *
- * @param Solar_Sql_Model_Collection $coll The record collection to set
- * pager info on.
- *
- * @param array $fetch The params for the original fetchAll() or
- * fetchAssoc().
- *
- * @return void
- */
- protected function _setCollectionPagerInfo($coll, $fetch)
- {
- $total = $this->countPages($fetch);
-
- $info = array(
- 'count' => (int) $total['count'],
- 'pages' => (int) $total['pages'],
- 'paging' => (int) $fetch['paging'],
- );
-
- if (! $info['count']) {
- $info['page'] = 0;
- $info['begin'] = 0;
- $info['end'] = 0;
- } elseif (! $fetch['page']) {
- $info['page'] = 1;
- $info['begin'] = 1;
- $info['end'] = $info['count'];
- } else {
- $start = (int) ($fetch['page'] - 1) * $fetch['paging'];
- $info['page'] = $fetch['page'];
- $info['begin'] = $start + 1;
- $info['end'] = $start + $info['count'];
- }
-
- $info['is_first'] = (bool) ($info['page'] == 1);
- $info['is_last'] = (bool) ($info['end'] == $info['count']);
-
- $coll->setPagerInfo($info);
- }
-
- /**
- *
- * Fetches one record by arbitrary parameters.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return Solar_Sql_Model_Record A record object.
- *
- */
- public function fetchOne($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('one', $fetch);
- if (! $result) {
- return null;
- }
-
- // get the main record, which sets the to-one data
- $record = $this->newRecord($result);
-
- // done
- return $record;
- }
-
- /**
- *
- * The same as fetchOne(), but returns an array instead of a record object.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return array
- *
- */
- public function fetchOneAsArray($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('one', $fetch);
- if (! $result) {
- return array();
- } else {
- return $result;
- }
- }
-
- /**
- *
- * Fetches a sequential array of values from the model, using only the
- * first column of the results.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return array
- *
- */
- public function fetchCol($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('col', $fetch);
- if ($result) {
- return $result;
- } else {
- return array();
- }
- }
-
- /**
- *
- * Fetches an array of key-value pairs from the model, where the first
- * column is the key and the second column is the value.
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return array
- *
- */
- public function fetchPairs($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('pairs', $fetch);
- if ($result) {
- return $result;
- } else {
- return array();
- }
- }
-
- /**
- *
- * Fetches a single value from the model (i.e., the first column of the
- * first record of the returned page set).
- *
- * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
- * fetch.
- *
- * @return mixed The single value from the model query, or null.
- *
- */
- public function fetchValue($fetch = null)
- {
- // fetch the result array and select object
- $fetch = $this->_fixFetchParams($fetch);
- list($result, $select) = $this->_fetchResultSelect('value', $fetch);
- return $result;
- }
-
- /**
- *
- * Returns a data result and the select used to fetch the data.
- *
- * If caching is turned on, this will fetch from the cache (if available)
- * and save the result back to the cache (if needed).
- *
- * @param string $type The type of fetch to perform: 'all', 'one', etc.
- *
- * @param Solar_Sql_Model_Params_Fetch $fetch The params for the fetch.
- *
- * @return array An array of two elements; element 0 is the result data,
- * element 1 is the Solar_Sql_Select object used to fetch the data.
- *
- */
- protected function _fetchResultSelect($type, Solar_Sql_Model_Params_Fetch $fetch)
- {
- $select = $this->newSelect($fetch);
-
- // attempt to fetch from cache?
- if ($fetch['cache']) {
- $key = $this->_cache->entry($fetch);
- $result = $this->_cache->fetch($key);
- if ($result !== false) {
- // found some data!
- return array($result, $select);
- }
- }
-
- // attempt to fetch from database
- $result = $select->fetch($type);
-
- // now process the results through the eagers
- foreach ($fetch['eager'] as $name => $eager) {
- $related = $this->getRelated($name);
- $related->modEagerResult($eager, $result, $type, $fetch);
- }
-
- // add to cache?
- if ($fetch['cache']) {
- $this->_cache->add($key, $result);
- }
-
- // done
- return array($result, $select);
- }
-
- /**
- *
- * Returns a new record with default values.
- *
- * @param array $spec An array of user-specified data to place into the
- * new record, if any.
- *
- * @return Solar_Sql_Model_Record A record object.
- *
- */
- public function fetchNew($spec = null)
- {
- $record = $this->_newRecord();
- $data = $this->_fetchNewData($spec);
- $record->initNew($this, $data);
- return $record;
- }
-
- /**
- *
- * Support method to generate the data for a new, blank record.
- *
- * @param array $spec An array of user-specified data to place into the
- * new record, if any.
- *
- * @return array An array of data for loading into a a new, blank record.
- *
- */
- protected function _fetchNewData($spec = null)
- {
- // the user-specifed data
- settype($spec, 'array');
-
- // the array of data for the record
- $data = array();
-
- // loop through each table column and collect default data
- foreach ($this->_table_cols as $key => $val) {
- if (array_key_exists($key, $spec)) {
- // user-specified
- $data[$key] = $spec[$key];
- } else {
- // default value
- $data[$key] = $val['default'];
- }
- }
-
- // loop through each calculate column and collect default data
- foreach ($this->_calculate_cols as $key => $val) {
- if (array_key_exists($key, $spec)) {
- // user-specified
- $data[$key] = $spec[$key];
- } else {
- // default value
- $data[$key] = $val['default'];
- }
- }
-
- // add Solar_Xml_Struct objects
- foreach ($this->_xmlstruct_cols as $key) {
- $data[$key] = Solar::factory($this->_xmlstruct_class);
- }
-
- // if we have inheritance, set that too
- if ($this->_inherit_name) {
- $key = $this->_inherit_col;
- $data[$key] = $this->_inherit_name;
- }
-
- // done
- return $data;
- }
-
- /**
- *
- * Fetches count and pages of available records.
- *
- * @param array $fetch An array of clauses for the SELECT COUNT()
- * statement, including 'where', 'group, and 'having'.
- *
- * @return array An array with keys 'count' and 'pages'; 'count' is the
- * number of records, 'pages' is the number of pages.
- *
- */
- public function countPages($fetch = null)
- {
- // fix up the parameters
- $fetch = $this->_fixFetchParams($fetch);
-
- // add a fake param called 'count' to make this different from the
- // orginating query (for cache deconfliction).
- $fetch['__count__'] = true;
-
- // check the cache
- if ($fetch['cache']) {
- $key = $this->_cache->entry($fetch);
- $result = $this->_cache->fetch($key);
- if ($result !== false) {
- // cache hit
- return $result;
- }
- }
-
- // clone the fetch for only the "keep" joins
- $clone = $fetch->cloneForKeeps();
-
- // get the base select
- $select = $this->newSelect($clone);
-
- // count on the primary column
- $col = "{$this->_model_name}.{$this->_primary_col}";
- $result = $select->countPages($col);
-
- // save in cache?
- if ($fetch['cache']) {
- $this->_cache->add($key, $result);
- }
-
- // done
- return $result;
- }
-
- // -----------------------------------------------------------------
- //
- // Select
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Converts and cleans-up fetch params from arrays to instances of
- * Solar_Sql_Model_Params_Fetch.
- *
- * @param array $spec The parameters for the fetch.
- *
- * @return Solar_Sql_Model_Params_Fetch
- *
- */
- protected function _fixFetchParams($spec)
- {
- if ($spec instanceof Solar_Sql_Model_Params_Fetch) {
- // already a params object, pre-empt further modification
- return $spec;
- }
-
- // baseline object
- $fetch = Solar::factory('Solar_Sql_Model_Params_Fetch');
-
- // defaults
- $fetch->load(array(
- 'cache' => $this->_config['auto_cache'],
- 'paging' => $this->_paging,
- 'alias' => $this->_model_name,
- ));
-
- // user specification
- $fetch->load($spec);
-
- // add columns if none already specified
- if (! $fetch['cols']) {
- $fetch->cols($this->_fetch_cols);
- }
-
- // done
- return $fetch;
- }
-
- /**
- *
- * Returns a WHERE clause array of conditions to use when fetching
- * from this model; e.g., single-table inheritance.
- *
- * @param array $where The WHERE array being modified.
- *
- * @param string $alias The current name of the table for this model
- * in the query being constructed; defaults to the model name.
- *
- * @return array The modified WHERE array.
- *
- */
- public function getConditions($alias = null)
- {
- // default to the model name for the alias
- if (! $alias) {
- $alias = $this->_model_name;
- }
-
- // the array of where clauses
- $where = array();
-
- // is inheritance on?
- if ($this->isInherit()) {
- $key = "{$alias}.{$this->_inherit_col} = ?";
- $val = $this->_inherit_name;
- $where = array($key => $val);
- }
-
- // done!
- return $where;
- }
-
- /**
- *
- * Returns a new Solar_Sql_Select tool, with the proper SQL object
- * injected automatically.
- *
- * @param Solar_Sql_Model_Params_Fetch|array $fetch Parameters for the
- * fetch.
- *
- * @return Solar_Sql_Select
- *
- */
- public function newSelect($fetch = null)
- {
- $fetch = $this->_fixFetchParams($fetch);
-
- if (! $fetch['alias']) {
- $fetch->alias($this->_model_name);
- }
-
- foreach ($fetch['eager'] as $name => $eager) {
- $related = $this->getRelated($name);
- $related->modEagerFetch($eager, $fetch);
- }
-
- $use_default_order = ! $fetch['order'] && $fetch['order'] !== false;
- if ($use_default_order && $this->_order) {
- $fetch->order("{$fetch['alias']}.{$this->_order}");
- };
-
- // get the select object
- $select = Solar::factory(
- $this->_select_class,
- array('sql' => $this->_sql)
- );
-
- // add the explicitly asked-for columns before the eager-join cols.
- // this is to make sure the fetchPairs() method works right, because
- // adding the eager columns first will mess that up.
- $select->from(
- "{$this->_table_name} AS {$fetch['alias']}",
- $fetch['cols']
- );
-
- $select->multiWhere($this->getConditions($fetch['alias']));
-
- // all the other pieces
- $select->distinct($fetch['distinct'])
- ->multiJoin($fetch['join'])
- ->multiWhere($fetch['where'])
- ->group($fetch['group'])
- ->multiHaving($fetch['having'])
- ->order($fetch['order'])
- ->setPaging($fetch['paging'])
- ->bind($fetch['bind']);
-
- // limit by count/offset, or by page?
- if ($fetch['limit']) {
- list($count, $offset) = $fetch['limit'];
- $select->limit($count, $offset);
- } else {
- $select->limitPage($fetch['page']);
- }
-
- // done!
- return $select;
- }
-
- // -----------------------------------------------------------------
- //
- // Record and Collection factories
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Returns the appropriate record object, honoring inheritance.
- *
- * @param array $data The data to load into the record.
- *
- * @return Solar_Sql_Model_Record A record object.
- *
- */
- public function newRecord($data)
- {
- // the record to return, eventually
- $record = null;
-
- // look for an inheritance in relation to $data
- $inherit = null;
- if ($this->_inherit_col && ! empty($data[$this->_inherit_col])) {
- // inheritance is available, and a value is set for the
- // inheritance column in the data
- $inherit = trim($data[$this->_inherit_col]);
- }
-
- // did we find an inheritance value?
- if ($inherit) {
- // try to find a model class based on inheritance, going up the
- // stack as needed. this checks for Current_Model_Type,
- // Parent_Model_Type, Grandparent_Model_Type, etc.
- //
- // blow up if we can't find it, since this is explicitly noted
- // as the inheritance class.
- $inherit_class = $this->_catalog->getClass($inherit);
-
- // if different from the current class, reset the model object.
- if ($inherit_class != $this->_class) {
- // use the inherited model class, it's different from the
- // current model. if it's not different, fall through, leaving
- // $record == null. that will invoke the logic below.
- $model = $this->_catalog->getModelByClass($inherit_class);
- $record = $model->newRecord($data);
- }
- }
-
- // do we have a record yet?
- if (! $record) {
- // no, because an inheritance model was not specified, or was of
- // the same class as this class.
- $record = $this->_newRecord();
- $record->init($this, $data);
- }
-
- return $record;
- }
-
- /**
- *
- * Returns a new record object for this model only.
- *
- * @return Solar_Sql_Model_Record A record object.
- *
- */
- protected function _newRecord()
- {
- if (empty($this->_record_prototype)) {
- // find the record class
- $record_class = $this->_stack->load('Record', false);
- if (! $record_class) {
- // use the default record class
- $record_class = $this->_record_class;
- }
- $this->_record_prototype = Solar::factory($record_class);
- }
- $record = clone $this->_record_prototype;
- return $record;
- }
-
- /**
- *
- * Returns the appropriate collection object for this model.
- *
- * @param array $data The data to load into the collection, if any.
- *
- * @return Solar_Sql_Model_Collection A collection object.
- *
- */
- public function newCollection($data = null)
- {
- $collection = $this->_newCollection();
- $collection->setModel($this);
- $collection->load($data);
- return $collection;
- }
-
- /**
- *
- * Returns a new collection object for this model only.
- *
- * @return Solar_Sql_Model_Collection A collection object.
- *
- */
- protected function _newCollection()
- {
- if (empty($this->_collection_prototype)) {
- // find the collection class
- $collection_class = $this->_stack->load('Collection', false);
- if (! $collection_class) {
- // use the default collection class
- $collection_class = $this->_collection_class;
- }
- $this->_collection_prototype = Solar::factory($collection_class);
- }
- $collection = clone $this->_collection_prototype;
- return $collection;
- }
-
- // -----------------------------------------------------------------
- //
- // Insert, update, or delete rows in the model.
- //
- // -----------------------------------------------------------------
-
- /**
- *
- * Inserts one row to the model table and deletes cache entries.
- *
- * @param array $data The row data to insert.
- *
- * @return int|bool On success, the last inserted ID if there is an
- * auto-increment column on the model (otherwise boolean true). On failure
- * an exception from PDO bubbles up.
- *
- * @throws Solar_Sql_Exception on failure of any sort.
- *
- * @see Solar_Sql_Model_Cache::deleteAll()
- *
- */
- public function insert($data)
- {
- if (! is_array($data)) {
- throw $this->_exception('ERR_DATA_NOT_ARRAY', array(
- 'method' => 'insert',
- ));
- }
-
- // reset affected rows
- $this->_affected_rows;
-
- // remove non-existent table columns from the data
- foreach ($data as $key => $val) {
- if (empty($this->_table_cols[$key])) {
- unset($data[$key]);
- // not in the table, so no need to check for autoinc
- continue;
- }
-
- // remove empty autoinc columns to soothe postgres, which won't
- // take explicit NULLs in SERIAL cols.
- if ($this->_table_cols[$key]['autoinc'] && empty($val)) {
- unset($data[$key]);
- }
- }
-
- // perform the insert and track affected rows
- $this->_affected_rows = $this->_sql->insert(
- $this->_table_name,
- $data
- );
-
- // does the table have an autoincrement column?
- $autoinc = null;
- foreach ($this->_table_cols as $name => $info) {
- if ($info['autoinc']) {
- $autoinc = $name;
- break;
- }
- }
-
- // return the last insert id, or just "true" ?
- if ($autoinc) {
- $id = $this->_sql->lastInsertId($this->_table_name, $autoinc);
- }
-
- // clear the cache for this model and related models
- $this->_cache->deleteAll();
-
- if ($autoinc) {
- return $id;
- } else {
- return true;
- }
- }
-
- /**
- *
- * Updates rows in the model table and deletes cache entries.
- *
- * @param array $data The row data to insert.
- *
- * @param string|array $where The WHERE clause to identify which rows to
- * update.
- *
- * @return int The number of rows affected.
- *
- * @throws Solar_Sql_Exception on failure of any sort.
- *
- * @see Solar_Sql_Model_Cache::deleteAll()
- *
- */
- public function update($data, $where)
- {
- if (! is_array($data)) {
- throw $this->_exception('ERR_DATA_NOT_ARRAY', array(
- 'method' => 'update',
- ));
- }
-
- // reset affected rows
- $this->_affected_rows = null;
-
- // don't update the primary key
- unset($data[$this->_primary_col]);
-
- // remove non-existent table columns from the data
- foreach ($data as $key => $val) {
- if (empty($this->_table_cols[$key])) {
- unset($data[$key]);
- }
- }
-
- // perform the update and track affected rows
- $this->_affected_rows = $this->_sql->update(
- $this->_table_name,
- $data,
- $where
- );
-
- // clear the cache for this model and related models
- $this->_cache->deleteAll();
-
- // done!
- return $this->_affected_rows;
- }
-
- /**
- *
- * Deletes rows from the model table and deletes cache entries.
- *
- * @param string|array $where The WHERE clause to identify which rows to
- * delete.
- *
- * @return int The number of rows affected.
- *
- * @see Solar_Sql_Model_Cache::deleteAll()
- *
- */
- public function delete($where)
- {
- // perform the deletion and track affected rows
- $this->_affected_rows = $this->_sql->delete(
- $this->_table_name,
- $where
- );
-
- // clear the cache for this model and related models
- $this->_cache->deleteAll();
-
- // done!
- return $this->_affected_rows;
- }
-
- /**
- *
- * Serializes data values in-place based on $this->_serialize_cols and
- * $this->_xmlstruct_cols.
- *
- * Does not attempt to serialize null values.
- *
- * If serializing fails, stores 'null' in the data.
- *
- * @param array &$data Record data.
- *
- * @return void
- *
- */
- public function serializeCols(&$data)
- {
- foreach ($this->_serialize_cols as $key) {
-
- // Don't process columns not in $data
- if (! array_key_exists($key, $data)) {
- continue;
- }
-
- // don't work on empty cols
- if (empty($data[$key])) {
- // Any empty value is canonicalized as null
- $data[$key] = null;
- continue;
- }
-
- $data[$key] = serialize($data[$key]);
- if (! $data[$key]) {
- // serializing failed
- $data[$key] = null;
- }
- }
-
- foreach ($this->_xmlstruct_cols as $key) {
-
- // Don't process columns not in $data
- if (! array_key_exists($key, $data)) {
- continue;
- }
-
- // don't work on empty cols
- if (empty($data[$…
Large files files are truncated, but you can click here to view the full file