/vendor/Mad/Model/Base.php
PHP | 3474 lines | 1761 code | 261 blank | 1452 comment | 141 complexity | 3a4a4c4ec80ad293ddd106ed696fcb3f MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * @category Mad
- * @package Mad_Model
- * @copyright (c) 2007-2009 Maintainable Software, LLC
- * @license http://opensource.org/licenses/bsd-license.php BSD
- */
- /**
- * Object Relation Mapper (ORM) Layer. Tables are represented as classes, rows in
- * the table correspond to objects from that class, and columns map to the object
- * attributes. Handles all basic CRUD operations (Create, Read, Update, Delete).
- *
- * Model subclasses should always be created with the generator to ensure creation of
- * all correct components (including data objects, unit tests, and fixtures):
- *
- * <code>
- * php ./script/generate.php model {ModelName} {table_name}
- * </code>
- *
- * @category Mad
- * @package Mad_Model
- * @copyright (c) 2007-2009 Maintainable Software, LLC
- * @license http://opensource.org/licenses/bsd-license.php BSD
- */
- abstract class Mad_Model_Base extends Mad_Support_Object
- {
- /*##########################################################################
- # Configuration options
- ##########################################################################*/
- /**
- * Should the table introspection data be cached
- * - true: Cache table introspection data to /tmp/cache/tables
- * - false: Introspect database table on every request
- */
- public static $cacheTables = true;
- /**
- * Include the root level in json serialization
- */
- public static $includeRootInJson = false;
- /*##########################################################################
- # Connection
- ##########################################################################*/
- /**
- * @var object
- */
- protected static $_connectionSpec;
- /**
- * @var array
- */
- protected static $_activeConnection;
- /**
- * @var Logger
- */
- protected static $_logger;
- /**
- * Database adapter instance
- * @var Mad_Model_ConnectionAdapter_Abstract
- */
- public $connection;
- /*##########################################################################
- # Attributes
- ##########################################################################*/
- /**
- * List of attributes excluded from mass assignment
- * @var array
- */
- protected $_attrProtected = array();
- /**
- * List of attribute name=>value pairs
- * @var array
- */
- protected $_attributes = array();
- /**
- * Name of this class
- * @var string
- */
- protected $_className = null;
- /**
- * Name of the database table
- * @var string
- */
- protected $_tableName = null;
- /**
- * Name of the primary key db column
- * @var string
- */
- protected $_primaryKey = null;
- /**
- * Has subclasses through a types table with class_name column
- * @var boolean
- */
- protected $_inheritanceColumn = 'type';
- /**
- * @var array
- */
- protected $_columns = array();
-
- /**
- * @var array
- */
- protected $_columnsHash = array();
-
- /**
- * @var array
- */
- protected $_columnNames = array();
-
- /**
- * An object cannot allow attribute access once it has been destroyed
- * @var boolean
- */
- protected $_frozen = false;
- /**
- * Is this a new record to be inserted?
- * @var boolean
- */
- protected $_newRecord = true;
- /*##########################################################################
- # Associations
- ##########################################################################*/
- /**
- * Has the association changed (even though the actual model might not have)
- * @var boolean
- */
- protected $_assocChanged = false;
- /**
- * A list of associations for this model define in concrete _initialize()
- * Lazy initialized if an unknown property/method is called
- *
- * @var array
- */
- protected $_associationList;
- /**
- * The list of association objects for this model
- * Lazy initialized if an unknown property/method is called
- *
- * @var array
- */
- protected $_associations;
- /**
- * The list of methods that are available for the associations of this model
- * $_associationMethods['createDocument'] = $hasOneAssociationObject;
- * This is lazy initialized if an unknown propery/method is called
- *
- * @var array
- */
- protected $_associationMethods;
- /*##########################################################################
- # Validations
- ##########################################################################*/
- /**
- * The list of validations that thie model enforces before an update/insert
- * @var array
- */
- protected $_validations = array();
-
- /**
- * Should we throw exceptions when validations fail
- * @var array
- */
- protected $_throw = false;
- /**
- * An array of messages stored when validations fail
- * @var array
- */
- public $errors;
- /*##########################################################################
- # Construct/Destruct
- ##########################################################################*/
- /**
- * Initialize any values given for the model.
- *
- * Load the model by attributes
- * <code>
- * <?php
- * ...
- * $attributes = array('documentname' => 'My Folder',
- * 'description' => 'My Description');
- * $folder = new Folder($attributes);
- * ...
- * ?>
- * </code>
- *
- * @param array $attributes construct by attribute list
- * @param array $options 'include' associations
- * @throws Mad_Model_Exception
- */
- public function __construct($attributes=null, $options=null)
- {
- $this->_className = get_class($this);
- // establish connection to db
- $this->connection = $this->retrieveConnection();
- $this->errors = new Mad_Model_Errors($this);
- // Initialize relationships/data-validation from subclass
- $this->_initialize();
- // init table/fields
- $this->_tableName = $this->tableName();
- $this->_primaryKey = $this->primaryKey();
- $this->_attributes = $this->_attributesFromColumnDefinition();
- // set values by attribute list
- if (isset($attributes)) {
- $this->setAttributes($attributes);
- }
- }
- /**
- * Clone the object without the values. All objects need to be explicitly
- * copied or we get them referencing the same data
- */
- public function __clone()
- {
- // reset attributes, errors, and associations
- $this->_attributes = $this->_attributesFromColumnDefinition();
- $this->errors->clear();
- $this->_resetAssociations();
- // only need to clone validations if they exist
- if (isset($this->_validations)) {
- foreach ($this->_validations as &$validation) {
- $validation = clone $validation;
- }
- }
- }
- /**
- * Initialize relationships and Data validation from subclass
- */
- abstract protected function _initialize();
- /*##########################################################################
- # Magic Accessor methods
- ##########################################################################*/
- /**
- * Dynamically get value for a attribute. Attributes cannot be retrieved once
- * an object has been destroyed.
- *
- * @param string $name
- * @return string
- * @throws Mad_Model_Exception
- */
- public function _get($name)
- {
- // active-record primary key value
- if ($name == 'id') { $name = $this->primaryKey(); }
- // active-record || attribute-reader value
- if (array_key_exists($name, $this->_attributes)) {
- return $this->readAttribute($name);
- }
- // dynamic attribute added by an association
- $this->_initAssociations();
- if (isset($this->_associationMethods[$name])) {
- return $this->_associationMethods[$name]
- ->callMethod($name, array());
- // unknown attribute
- } else {
- throw new Mad_Model_Exception("Unrecognized attribute '$name'");
- }
- }
- /**
- * Dynamically set value for a attribute. Attributes cannot be set once an
- * object has been destroyed. Primary Key cannot be changed if the data was
- * loaded from a database row
- *
- * @param string $name
- * @param mixed $value
- * @throws Mad_Model_Exception
- */
- public function _set($name, $value)
- {
- if ($this->_frozen) {
- $msg = "You cannot set attributes of a destroyed object";
- throw new Mad_Model_Exception($msg);
- }
- // active-record primary key value
- if ($name == 'id') { $name = $this->primaryKey(); }
- // cannot change pk if it's already set
- if (($name == $this->primaryKey()) && !$this->isNewRecord()) {
- // ignore assignment of pk so that this works with activeresource
- return;
- }
- // active-record || attribute-reader value
- if (array_key_exists($name, $this->_attributes)) {
- return $this->writeAttribute($name, $value);
- }
- // dynamic attribute added by an association
- $this->_initAssociations();
- if (isset($this->_associationMethods[$name.'='])) {
- return $this->_associationMethods[$name.'=']
- ->callMethod($name.'=', array($value));
- // unknown attribute
- } else {
- throw new Mad_Model_Exception("Unrecognized attribute '$name'");
- }
- }
- /**
- * Allows testing with empty() and isset() to work inside templates
- *
- * @param string $key
- * @return boolean
- */
- public function _isset($name)
- {
- // association methods
- $this->_initAssociations();
- if (isset($this->_associationMethods[$name])) {
- return count($this->_get($name)) > 0;
- // active-record attribue
- } else {
- return isset($this->_attributes[$name]);
- }
- return isset($this->_attributes[$name]);
- }
- /**
- * Association methods are added at runtime and use dynamic methods.
- *
- * @param string $name
- * @param array $args
- */
- public function __call($name, $args)
- {
- // dynamic attribute added by an association
- $this->_initAssociations();
- if (isset($this->_associationMethods[$name])) {
- return $this->_associationMethods[$name]->callMethod($name, $args);
- // unknown method
- } else {
- throw new Mad_Model_Exception("Unrecognized method '$name'");
- }
- }
- /**
- * Print out a string describing this object's attributes
- *
- * @return string
- */
- public function __toString()
- {
- foreach ($this->_attributes as $name => $value) {
- $str[] = "$name => ".(isset($value) ? "'$value'" : 'null');
- }
- return isset($str) ? "\n".$this->_className." Object: \n".join(", \n", $str) : null;
- }
-
- /*##########################################################################
- # Serialization
- ##########################################################################*/
-
- /**
- * Serialize only needs attributes
- */
- public function __sleep()
- {
- return array('_attributes', '_attrReaders',
- '_attrWriters', '_attrValues');
- }
- /**
- * Enables models to be used as URL parameters for routes automatically.
- *
- * @return null|string
- */
- public function toParam()
- {
- $pk = $this->primaryKey();
- if ($pk && isset($this->_attributes[$pk])) {
- return (string)$this->_attributes[$pk];
- }
- }
- /*##########################################################################
- # Logger
- ##########################################################################*/
- /**
- * Set a logger object, defaulting to mad_default_logger. This needs to
- * reset connection so that the correct log is passed to the connection
- * adapter.
- *
- * @param object $logger
- */
- public static function setLogger($logger=null)
- {
- self::$_logger = isset($logger) ? $logger : $GLOBALS['MAD_DEFAULT_LOGGER'];
- self::establishConnection(self::removeConnection());
- }
- /**
- * Returns the logger object.
- *
- * @return object
- */
- public static function logger()
- {
- // set default logger
- if (!isset(self::$_logger)) {
- self::setLogger();
- }
- return self::$_logger;
- }
- /*##########################################################################
- # Connection Management
- ##########################################################################*/
- /**
- * Establishes the connection to the database. Accepts a hash as input where
- * the :adapter key must be specified with the name of a database adapter (in lower-case)
- *
- * Example for regular databases (MySQL, Postgresql, etc):
- * <code>
- * Mad_Model_Base::establishConnection(array(
- * "adapter" => "mysql",
- * "host" => "localhost",
- * "username" => "myuser",
- * "password" => "mypass",
- * "database" => "somedatabase"
- * ));
- * </code>
- *
- * Example for SQLite database:
- * <code>
- * Mad_Model_Base::establishConnection(array(
- * "adapter" => "sqlite",
- * "database" => "path/to/dbfile"
- * ));
- * </code>
- *
- * The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
- * may be returned on an error.
- *
- * @param array $spec
- * @return Connection
- */
- public static function establishConnection($spec=null)
- {
- // $spec is empty: $spec defaults to MAD_ENV string like "development"
- // keep going to read YAML for this environment string
- if (empty($spec)) {
- if ( !defined('MAD_ENV') || !MAD_ENV ) {
- throw new Mad_Model_Exception('Adapter Not Specified');
- }
- $spec = MAD_ENV;
- }
- // $spec is string: read YAML config for environment named by string
- // keep going to process the resulting array
- if (is_string($spec)) {
- $config = Horde_Yaml::loadFile(MAD_ROOT.'/config/database.yml');
- $spec = $config[$spec];
- }
- // $spec is an associative array
- if (is_array($spec)) {
-
- // validation of array is handled by horde_db
- self::$_connectionSpec = $spec;
- } else {
- throw new Mad_Model_Exception("Invalid Connection Specification");
- }
- }
- /**
- * Returns true if a connection that's accessible to this class have already
- * been opened.
- *
- * @return boolean
- */
- public static function isConnected()
- {
- return isset(self::$_activeConnection);
- }
- /**
- * Locate/Activate the connection
- *
- * @return Mad_Model_ConnectionAdapter_Abstract
- */
- public static function retrieveConnection()
- {
- // already have active connection
- if (self::$_activeConnection) {
- $conn = self::$_activeConnection;
- // connection based on spec
- } elseif ($spec = self::$_connectionSpec) {
- if (empty($spec['logger'])) {
- $spec['logger'] = self::logger();
- }
- $adapter = Horde_Db_Adapter::getInstance($spec);
- $conn = self::$_activeConnection = $adapter;
- }
- if (empty($conn)) {
- throw new Mad_Model_Exception("Connection Not Established");
- }
- return $conn;
- }
- /**
- * Remove the connection for this class. This will close the active
- * connection and the defined connection (if they exist). The result
- * can be used as argument for establishConnection, for easy
- */
- public static function removeConnection()
- {
- $spec = self::$_connectionSpec;
- $conn = self::$_activeConnection;
- self::$_connectionSpec = null;
- self::$_activeConnection = null;
- if ($conn) { $conn->disconnect(); }
- return $spec ? $spec : '';
- }
- /**
- * Returns the connection currently associated with the class. This can
- * also be used to "borrow" the connection to do database work unrelated
- * to any of the specific Active Records.
- *
- * @return Mad_Model_ConnectionAdapter_Abstract
- */
- public static function connection()
- {
- if (self::$_activeConnection) {
- return self::$_activeConnection;
- } else {
- return self::$_activeConnection = self::retrieveConnection();
- }
- }
- /*##########################################################################
- # DB Table column/keys
- ##########################################################################*/
- /**
- * Get the name of the table
- * @return string
- */
- public function tableName()
- {
- if (isset($this->_tableName)) {
- return $this->_tableName;
- } else {
- return $this->resetTableName();
- }
- }
-
- /**
- * Reset the table name based on conventions
- *
- */
- public function resetTableName()
- {
- return $this->_tableName =
- Mad_Support_Inflector::tableize($this->baseClass());
- }
- /**
- * Get the name of the primary key column
- * @return string
- */
- public function primaryKey()
- {
- if (isset($this->_primaryKey)) {
- return $this->_primaryKey;
- } else {
- return $this->resetPrimaryKey();
- }
- }
-
- /**
- * Rest primary key name based on conventions.
- */
- public function resetPrimaryKey()
- {
- return $this->_primaryKey = 'id';
- }
- /**
- * Get class name column used for single-table inheritance
- *
- * @return string
- */
- public function inheritanceColumn()
- {
- return $this->_inheritanceColumn;
- }
- /**
- * Set the name of the table for the model
- * @param string $table
- */
- public function setTableName($value)
- {
- $this->_tableName = $value;
- }
- /**
- * Set the name of the table for the model
- * @param string $value
- */
- public function setPrimaryKey($value)
- {
- $this->_primaryKey = $value;
- }
- /**
- * Change the default column used for single-table inheritance
- * @param string $col
- */
- public function setInheritanceColumn($col)
- {
- $this->_inheritanceColumn = $col;
- }
- /**
- * Returns an array of column objects for the table associated
- * with this class.
- *
- * @return array
- */
- public function columns()
- {
- if (empty($this->_columns)) {
- $this->_columns = $this->connection->columns($this->tableName(),
- "$this->_className Columns");
- foreach ($this->_columns as $col) {
- $col->setPrimary($col->getName() == $this->_primaryKey);
- }
- }
- return $this->_columns;
- }
- /**
- * Returns a hash of column objects for the table associated with
- * this class.
- *
- * @return array
- */
- public function columnsHash()
- {
- if (empty($this->_columnsHash)) {
- foreach ($this->columns() as $col) {
- $this->_columnsHash[$col->getName()] = $col;
- }
- }
- return $this->_columnsHash;
- }
- /**
- * Returns an array of column names as strings.
- *
- * @return array
- */
- public function columnNames()
- {
- if (empty($this->_columnNames)) {
- foreach ($this->columns() as $col) {
- $this->_columnNames[] = $col->getName();
- }
- }
- return $this->_columnNames;
- }
- /**
- * Reset the column info
- */
- public function resetColumnInformation()
- {
- $this->_columns = $this->_columnsHash =
- $this->_columnNames = $this->_inheritanceColumn = null;
- }
- /**
- * Get the base class for this model. Defined by subclass
- *
- * @return string
- */
- public function baseClass()
- {
- // go up single hierarchy if this is an STI model
- $parentClass = get_parent_class($this);
- if ($parentClass != 'Mad_Model_Base') {
- return $parentClass;
- }
- return $this->_className;
- }
- /*##########################################################################
- # Attributes
- ##########################################################################*/
- /**
- * Set list of attributes protected from mass assignment
- *
- * @todo implement this in save statements
- * @param string $attribute
- */
- public function attrProtected($attributes)
- {
- $names = func_get_args();
- $this->_attrProtected = array_unique(
- array_merge($this->_attrProtected, $names));
- }
- /**
- * Get the value for an attribute in this model
- *
- * @param string $name
- * @return string
- */
- public function readAttribute($name)
- {
- // active-record attributes
- if (array_key_exists($name, $this->_attributes)) {
- return $this->_attributes[$name];
- // no value set yet
- } else {
- return null;
- }
- }
- /**
- * Set the value for an attribute in this model
- *
- * @param string $name
- * @param mixed $value
- */
- public function writeAttribute($name, $value)
- {
- // active-record attributes
- if (array_key_exists($name, $this->_attributes)) {
- $this->_attributes[$name] = $value;
- }
- }
- /**
- * Get the human attribute name for a given attribute
- *
- * @return string
- */
- public function humanAttributeName($attr)
- {
- $col = $this->columnForAttribute($attr);
- return Mad_Support_Inflector::humanize($col->getName());
- }
- /**
- * Get the array of attribute fields
- * @return array
- */
- public function getAttributes()
- {
- return $this->_attributes;
- }
-
- /**
- * Mass assign attributes for this model
- * @param array $attributes
- */
- public function setAttributes($attributes = array())
- {
- // Set attributes by array
- if (is_array($attributes)) {
- foreach ($attributes as $attribute => $value) {
- $this->$attribute = $value;
- }
- // Set primary key (Beware this does not instantiate other properties)
- } elseif (is_numeric($attributes)) {
- $this->{$this->primaryKey()} = $attributes;
- }
- }
- /**
- * Finder methods must instantiate through this method to work with the
- * single-table inheritance model that makes it possible to create
- * objects of different types from the same table.
- *
- * @param array $record
- */
- public function instantiate($record)
- {
- // single table inheritance
- $column = $this->inheritanceColumn();
- if (isset($record[$column]) && $className = $record[$column]) {
- if (!class_exists($className)) {
- $msg = "The single-table inheritance mechanism failed to ".
- "locate the subclass: '$className'. This error is raised ".
- "because the column '$column' is reserved for storing the ".
- "class in case of inheritance. Please rename this column ".
- "if you didn't intend it to be used for storing the ".
- "inheritance class.";
- throw new Mad_Model_Exception($msg);
- }
- $model = new $className;
- } else {
- $model = clone $this;
- }
- return $model->setValuesByRow($record);
- }
- /**
- * Set the values for this object using a db result set.
- *
- * <code>
- * <?php
- * ...
- * $folder = new Folder();
- * $row = $result->fetchRow();
- * $folder->setValuesByRow($row)
- * ...
- * ?>
- * </code>
- *
- * @param array $dbValues
- * @return Mad_Model_Base
- */
- public function setValuesByRow($values)
- {
- // active-record attributes
- foreach ($this->_attributes as $name => $value) {
- if (array_key_exists($name, $values)) {
- $this->writeAttribute($name, $values[$name]);
- }
- }
- // attr-writers
- foreach ($this->_attrWriters as $name) {
- if (array_key_exists($name, $values)) {
- $this->$name = $values[$name];
- }
- }
- // this isn't a new record if we've loaded it from the db
- $this->_newRecord = false;
- return $this;
- }
- /**
- * Returns an array of names for the attributes available on this
- * object sorted alphabetically.
- *
- * @return array
- */
- public function attributeNames()
- {
- $attrs = array_keys($this->_attributes);
- sort($attrs);
- return $attrs;
- }
- /**
- * Returns the column object for the named attribute
- *
- * @param string $name
- * @return object
- */
- public function columnForAttribute($name)
- {
- $colHash = $this->columnsHash();
- return $colHash[$name];
- }
- /*##########################################################################
- # Deprecated column accessors
- ##########################################################################*/
- /**
- * Get an array of columns
- * @deprecated
- * @param string $tblAlias prepend table alias to columns
- * @param boolean $colAlias Generate column aliases for TO_CHAR()s
- * @return array
- */
- public function getColumns($tblAlias=null, $colAlias=true)
- {
- $tblAlias = isset($tblAlias) ? "$tblAlias." : null;
- foreach ($this->_attributes as $name => $value) {
- $cols[] = $tblAlias.($name);
- }
- return isset($cols) ? $cols : array();
- }
- /**
- * Construct the column string from the columns. Convert timestamps to string (TO_CHAR)
- * @deprecated
- * @param string $tblAlias prepend table alias to columns
- * @param boolean $colAlias Generate column aliases for TO_CHAR()s
- * @return string
- */
- public function getColumnStr($tblAlias=null, $colAlias=true)
- {
- foreach ($this->getColumns($tblAlias, $colAlias) as $col) {
- $parts = explode('.', $col);
- // has table alias
- if (isset($parts[1])) {
- $quoted[] = $this->connection->quoteColumnName($parts[0]).'.'.
- $this->connection->quoteColumnName($parts[1]);
- // column only
- } else {
- $quoted[] = $this->connection->quoteColumnName($parts[0]);
- }
- }
- return join(', ', $quoted);
- }
- /**
- * Get the insert values string from the columns.
- * @deprecated
- * @return string
- */
- public function getInsertValuesStr()
- {
- $vals = array();
- foreach ($this->_attributes as $name => $value) {
- $vals[] = $this->_quoteValue($value);
- }
- return join(', ', $vals);
- }
- /*##########################################################################
- # Associations
- ##########################################################################*/
- /**
- * Returns the Association object for the named association
- *
- * @param string $name
- * @return Mad_Model_Association_Base
- */
- public function reflectOnAssociation($name)
- {
- $this->_initAssociations();
- if (! isset($this->_associations[$name])) {
- throw new Mad_Model_Exception("Association $name does not exist for ".get_class($this));
- }
- return $this->_associations[$name];
- }
- /**
- * Since the value associated with the association has change, force it to
- * reload
- */
- public function reloadAssociation($name)
- {
- if (isset($this->_associationMethods)) {
- $this->_associationMethods = null;
- $this->_associations = null;
- }
- }
- /**
- * Set as association as being loaded
- * @param string $name
- */
- public function setAssociationLoaded($name)
- {
- $this->_initAssociations();
- if (isset($this->_associations[$name])) {
- $this->_associations[$name]->setLoaded();
- }
- }
- /*##########################################################################
- # CRUD Class methods
- ##########################################################################*/
- /**
- * <b>FIND BY PRIMARY KEY.</b>
- *
- * <code>
- * $binder = Binder::find(123);
- * $binders = Binder::find(array(123, 234));
- * </code>
- *
- *
- * <b>FIND ALL</b>
- *
- * Retrieve using WHERE conditions using SQL:
- * <code>
- * $binders = Binder::find('all', array(
- * 'conditions' => "name = 'Stubbed Images'")
- * );
- * </code>
- *
- * Retrieve using WHERE conditions and LIMIT:
- * <code>
- * $binders = Binder::find('all', array('conditions' => 'name = :name',
- * 'order' => 'name DESC'
- * 'limit' => 10),
- * array(':name' => 'Stubbed Images'));
- * </code>
- *
- * Retrieve using WHERE conditions and OFFSET (same as mysql LIMIT 20, 10):
- * <code>
- * $binders = Binder::find('all', array('conditions' => 'name = :name',
- * 'order' => 'name DESC'
- * 'offset' => 20,
- * 'limit' => 10),
- * array(':name' => 'Stubbed Images'));
- * </code>
- *
- * Retrieve using WHERE conditions and FROM tables:
- * <code>
- * $folders = Folder::find('all', array('conditions' => 'f.folderid=d.parent_folderid',
- * 'from' => 'folders f, documents d'));
- * </code>
- *
- *
- * <b>FIND FIRST</b>
- *
- * Find the first record that matches the given criteria. (same options as find('all')
- * <code>
- * $binder = Binder::find('first', array('conditions' => 'f.folderid=d.parent_folderid',
- * 'from' => 'folders f, documents d'));
- * </code>
- *
- *
- * @param mixed $type (pk/pks/all/first/count)
- * @param array $options
- * @param array $bindVars
- * @throws Mad_Model_Exception_RecordNotFound
- */
- public static function find($type, $options=null, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_find($type, $options, $bindVars);
- }
- /**
- * A convenience wrapper for find('first'). You can pass in all the
- * same arguments to this method as you can to find('first').
- *
- * @see Mad_Model_Base::find()
- *
- * @param array $options
- * @param array $bindVars
- */
- public static function first($options=null, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_find('first', $options, $bindVars);
- }
- /**
- * Count how many records match the given criteria
- * <code>
- * $binderCnt = Binder::count(array('name' => 'Stubbed Images'));
- * </code>
- */
- public static function count($options=null, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_count($options, $bindVars);
- }
- /**
- * This method provides an interface for finding records using direct sql instead of
- * the componentized api of find(). This is however not always desired as find() does
- * some magic that this method cannot do.
- *
- * <b>FIND ALL RECORDS BY SQL</b>
- *
- * <code>
- * $sql = 'SELECT *
- * FROM briefcases
- * WHERE name=:name';
- * $collection = Binder::findBySql('all', $sql, array(':name'=>'Stubbed Images'));
- * </code>
- *
- *
- * <b>FIND FIRST RECORD BY SQL</b>
- *
- * <code>
- * $sql = 'SELECT *
- * FROM briefcases
- * WHERE name=:name';
- * $binder = Binder::findBySql('first', $sql, array(':name'=>'Stubbed Images'));
- * </code>
- *
- *
- * @param string $type
- * @param string $sql
- * @param array $bindVars
- */
- protected static function findBySql($type, $sql, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_findBySql($type, $sql, $bindVars);
- }
- /**
- * This method provides an interface for counting records using direct sql
- * instead of the componentized api of find(). This is however not always
- * desired as find() does some magic that this method cannot do.
- *
- * <b>COUNT RECORDS BY SQL</b>
- *
- * <code>
- * $sql = 'SELECT COUNT(1)
- * FROM briefcases
- * WHERE name=:name';
- * $binder = Binder::countBySql($sql, array(':name'=>'Stubbed Images'));
- * </code>
- *
- * @param string $sql
- * @param array $bindVars
- */
- protected static function countBySql($sql, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_countBySql($sql, $bindVars);
- }
- /**
- * Paginate records for find()
- *
- * @param array $options
- * @param array $bindVars
- * @return Mad_Model_Collection
- */
- protected static function paginate($options=null, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_paginate($options, $bindVars);
- }
- /**
- * Check if this record exists.
- *
- * <code>
- * $folderExists = Folder::exists(123);
- * </code>
- *
- * @param int $id
- * @return boolean
- */
- public static function exists($id)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_exists($id);
- }
- /**
- * Create a new record in the db from the attributes of the model
- *
- * Create single record
- * <code>
- * $binder = Binder::create(array('name' => "derek's binder"));
- * </code>
- *
- * Create multiple records
- * <code>
- * $binders = Binder::create(array(array('name' => "derek's binder"),
- * array('name' => "dallas' binder")));
- * </code>
- *
- * @param array $attributes
- * @return mixed single model object OR array of model objects
- */
- public static function create($attributes)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_create($attributes);
- }
- /**
- * Update record in the db directly by pk or array of pks
- *
- * Single record update
- * <code>
- * $binder = Binder::update(123, array('name' => 'My new name'));
- * </code>
- *
- * Multiple record update
- * <code>
- * $binders = Binder::update(array(123, 456), array('name' => 'My new name'));
- * </code>
- *
- * @param int $id
- * @param array $attributes
- * @return void
- */
- public static function update($id, $attributes=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_update($id, $attributes);
- }
- /**
- * Delete record(s) from the database by primary key
- *
- * Delete single record
- * <code>
- * Binder::delete(123);
- * </code>
- *
- * Delete multiple records
- * <code>
- * Binder::delete(array(123, 234));
- * </code>
- *
- * @param mixed $id (int or array of ints)
- * @return void
- */
- public static function delete($id)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_delete($id);
- }
- /**
- * Update multiple records that match the given conditions
- *
- * <code>
- * Binder::update("description = 'my tests'", 'name = :name',
- * array(':name' => 'My test binder'));
- * </code>
- *
- * @param string $set
- * @param string $conditions
- * @param array $bindVars
- * @return void
- */
- public static function updateAll($set, $conditions=null, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_updateAll($set, $conditions, $bindVars);
- }
- /**
- * Delete multiple records that match the given conditions
- *
- * <code>
- * Binder::delete('name = :name', array(':name' => 'My test binder'));
- * </code>
- *
- * @param string $conditions
- * @param array $bindVars
- */
- public static function deleteAll($conditions=null, $bindVars=null)
- {
- // hack to get name of this class (because of static)
- $bt = debug_backtrace();
- $m = new $bt[1]['class'];
- return $m->_deleteAll($conditions, $bindVars);
- }
- /*##########################################################################
- # CRUD Instance methods
- ##########################################################################*/
- /**
- * Save data stored in memory (the object) back into the database. Performs either
- * an insert or an update depending on if this is a new record
- *
- * Insert a row
- * <code>
- * $binder = new Binder(array('name' => "Derek's binder"));
- * $binder->save();
- * </code>
- *
- * Update a row
- * <code>
- * $binder = Binder::find(123);
- * $binder->name = "Derek's updated binder";
- * $binder->save();
- * </code>
- *
- * @return mixed boolean or Mad_Model_Base
- * @throws Mad_Model_Exception_Validation
- */
- public function save()
- {
- // All saves are atomic - only start transaction if one hasn't been
- $started = $this->connection->transactionStarted();
- if (!$started) { $this->connection->beginDbTransaction(); }
- try {
- // save associated models this model depends on & validate data
- $this->_saveAssociations('before');
- $this->_validateData();
- $this->_createOrUpdate();
- $this->_saveAssociations('after');
- $this->_newRecord = false;
- if (!$started) { $this->connection->commitDbTransaction(); }
- $this->_throw = false;
- return $this;
- } catch (Exception $e) {
- $this->connection->rollbackDbTransaction();
- if ($this->_throw) {
- $this->_throw = false;
- throw $e;
- }
- return false;
- }
- }
- /**
- * Attempts to save the record, but instead of just returning false if it
- * couldn't happen, it throws a Mad_Model_Exception_Validation
- *
- * @see Mad_Model_Base::save()
- *
- * @return object
- * @throws Mad_Model_Exception_Validation
- */
- public function saveEx()
- {
- $this->_throw = true;
- $this->save();
- }
- /**
- * Update specific attributes for the current object
- *
- * Update single attribute
- * <code>
- * $binder = Binder::find(123);
- * $binder->updateAttributes('name', 'My New Briefcase');
- * </code>
- *
- * @param string $name
- * @param string $value
- * @return void
- */
- public function updateAttribute($name, $value)
- {
- $this->$name = $value;
- return $this->save();
- }
- /**
- * Update multiple attributes for the current object
- *
- * Update multiple attributes
- * <code>
- * $binder = Binder::find(123);
- * $binder->updateAttributes(array('name' => 'The new name',
- * 'description' => 'The new description'));
- * </code>
- *
- * @param array|Traversable $attributes
- * @return void
- */
- public function updateAttributes($attributes)
- {
- if (! is_array($attributes)) {
- if (! $attributes instanceof Traversable) {
- return false;
- }
- }
- foreach ($attributes as $attribute => $value) {
- $this->$attribute = $value;
- }
- return $this->save();
- }
- /**
- * Destroy a record (delete from db)
- *
- * A custom implementation of destroy() can be written for a model by overriding
- * the _destroy() method. This will ensure that all callbacks are still executed
- *
- * <code>
- * $binder = Binder::find(123);
- * $binder->destroy();
- * </code>
- *
- * @return boolean
- */
- public function destroy()
- {
- // All deletes are atomic
- $started = $this->connection->transactionStarted();
- if (!$started) { $this->connection->beginDbTransaction(); }
- try {
- $this->_beforeDestroy();
- $this->_destroy();
- $this->_afterDestroy();
- if (!$started) { $this->connection->commitDbTransaction(); }
- return true;
- } catch (Exception $e) {
- $this->connection->rollbackDbTransaction(false);
- return false;
- }
- }
- /**
- * Replace bind variables in the sql string.
- *
- * @param string $sql
- * @param array $bindVars
- */
- public function sanitizeSql($sql, $bindVars)
- {
- preg_match_all("/(:\w+)/", $sql, $matches);
- if (!isset($matches[1])) return;
- foreach ($matches[1] as $replacement) {
- if (!array_key_exists($replacement, $bindVars)) {
- $msg = "missing value for $replacement in $sql";
- throw new Mad_Model_Exception($msg);
- }
- $sql = str_replace(
- $replacement,
- $this->_quoteValue($bindVars[$replacement]),
- $sql
- );
- }
- return $sql;
- }
- /**
- * Reload values from the database
- */
- public function reload()
- {
- $model = $this->find($this->id);
- foreach ($model->getAttributes() as $name => $value) {
- $this->writeAttribute($name, $value);
- }
- // reset associations
- if (isset($this->_associations)) {
- foreach ($this->_associations as $assoc) {
- $assoc->setLoaded(false);
- }
- }
-
- return $this;
- }
- /**
- * Check if this is a record that hasn't been inserted yet
- *
- * @return boolean
- */
- public function isNewRecord()
- {
- return $this->_newRecord;
- }
- /**
- * This flag allows us to set explicitly that the association has changed and needs
- * to be saved even if the object itself hasn't been changed
- *
- * @param boolean $assocSaved
- */
- public function setIsAssocChanged($assocChanged=true)
- {
- $this->_assocChanged = $assocChanged;
- }
- /**
- * Check if the association has changed
- *
- * @return boolean
- */
- public function isAssocChanged()
- {
- return $this->_assocChanged;
- }
- /**
- * Check if this object is frozen for modification
- *
- * @return boolean
- */
- public function isDestroyed()
- {
- return $this->_frozen;
- }
- /*##########################################################################
- # Associations - These are set in _initialize() method of subclass
- ##########################################################################*/
- /**
- * This defines a one-to-one relationship with another model class. It declares
- * that the given class has a parent relationship to this model.
- *
- * The foreign key must be specified in the options of the declaration
- * using 'foreignKey'
- *
- * For Document model
- * <code>
- * <?php
- * ...
- * protected function _initialize()
- * {
- * // specify that the Document has a parent Folder
- * $this->belongsTo('Folder', array('foreignKey' => 'parent_folderid'));
- * }
- * ...
- * ?>
- * </code>
- *
- * When we specify this relationship, special attributes and methods are dynamically
- * added to the Document model.
- *
- *
- * Access the parent folder. This performs a query to get the parent folder
- * object of the document.
- *
- * <code>
- * <?php
- * ...
- * // the very verbose..
- * $folderId = $document->parent_folderid;
- * $parentFolder = Folder::find($folderId);
- * $folderName = $parentFolder->folder_name;
- *
- * // can now be simply written as
- * $folderName = $document->folder->folder_name;
- * ...
- * ?>
- * </code>
- *
- * The parent class name is assumed to be the mixed-case singular form of the
- * class name. The association name however can be defined as any name you wish
- * by specifying 'className' option.
- *
- * For Document model
- * <code>
- * <?php
- * ...
- * protected function _initialize()
- * {
- * // specify that the Document has a parent Folder
- * $this->belongsTo('Parent', array('foreignKey' => 'parent_folderid',
- * array('className' => 'Folder')));
- * }
- * ...
- * // now we can access the property using the name 'parent'
- * $parentFolder = $document->parent;
- * ...
- * ?>
- * </code>
- *
- * @param string $associationId
- * @param array $options
- */
- protected function belongsTo($associationId, $options=null)
- {
- $this->_addAssociation('belongsTo', $associationId, $options);
- }
- /**
- * This defines a one-to-one relationship with another model class. It declares
- * that a given class is a child of this model.
- *
- * The foreign key must be specified in the options of the declaration using
- * 'foreignKey'. This declaration defines the same set of methods in the model
- * object as belongsTo, So given the MdMetadata class example..
- *
- * Any given metadata can have a single icon associated with it
- *
- * For MdMetadata model
- * <code>
- * <?php
- * ...
- * protected function _initialize()
- * {
- * // specify that the Metadata has an associated metadata icon
- * $this->hasOne('MdIcon');
- * }
- * ...
- * ?>
- * </code>
- *
- * Now we can refer to the new object through the association
- * <code>
- * <?php
- * ...
- * // the very verbose..
- * $metadataId = $metadata->metadataid;
- * $mdIcon = MdIcon::find($metadataId);
- * $altText = $mdIcon->alt_text;
- *
- * // can now be simply written as
- * $altText = $metadata->mdIcon->alt_text;
- * ...
- * ?>
- * </code>
- *
- * The child class name is assumed to be the mixed-case singular form of the
- * class name. The association name however can be defined as any name you wish
- * by specifying 'className' option similar to belongsTo().
- *
- * Another options available to hasOne is 'dependent'. You can define if the associated
- * object is dependent on this object existing. This can be one of two options,
- * 1. destroy (the default)
- * 2. nullify
- *
- * A metadata Icon can't exist without it's associated metadata. Because of this, we
- * can tell metadata to destroy all metadata icons before
- *
- * @see Mad_Model_Base::belongsTo()
- *
- * @param string $associationId
- * @param array $options
- */
- protected function hasOne($associationId, $options=null)
- {
- $this->_addAssociation('hasOne', $associationId, $options);
- }
- /**
- * This defines a one-to-many relationship with another model class.
- * Define an attribute that behaves like a collection of the child objects.
- *
- * The foreign key must be specified in the options of the declaration using
- * 'foreignKey'. Ordering of children objects can also be specified using the
- * 'order' option.
- *
- * The child class name is assumed to be the mixed-case plural form of the
- * class name. The association name however can be defined as any name you wish
- * by specifying 'className' option similar to belongsTo()
- *
- * For Folder model with multiple documents
- * <code>
- * <?…
Large files files are truncated, but you can click here to view the full file