* * @license http://opensource.org/licenses/bsd-license.php BSD * */ /** * * Load super-class to handle exceptions, locale, etc. * * @ignore */ Solar::loadClass('Solar_Base', true); /** * * Load required abstract Meta object * * @ignore */ Solar::loadClass('Domain51_Model_Meta_Abstract', true); /** * * An abstract version of the Model within the Domain51 MVC framework * * This provides the basic interface for reporting properties, tracking changes, * insuring properties are within the defined Meta information, * {@link revert()}ing changes made before {@link save()} is called, and finally * {@link save()}ing the properties. * * None of the actual loading/saving happens within the Model, instead the * callback object set via {@link attach()} is provided with this object in * order to determine what should be saved. * * * @package Domain51 * * @subpackage Domain51_Model * * @author Travis Swicegood * * @license http://opensource.org/licenses/bsd-license.php BSD * */ abstract class Domain51_Model_Abstract extends Solar_Base { /** * * An array of properties that this Model represents as returned by * {@link __get()}. * * * @var array * * @access protected */ protected $_data = array(); /** * * An array of all of the changes to the various properties * * * @var array * * @access protected * */ protected $_changes = array(); /** * * A link to the {@link Domain51_Model_Meta} object that represents this * Model. * * * @var array * * @access protected * */ protected $_meta = null; /** * * A link to a valid callback that will be called on {@link save()} * * * @var mixed * * @access protected * * @see attach() * */ protected $_callback = null; /** * * Handle instantiation * * This is called by {@link Domain51_Model_Mapper} when a new Model is * fetched. * * @param array $config An associative array of data that represents this * model. * * @return void * */ public function __construct($config = null) { parent::__construct($config); // we must have a meta object in the config and it must be of a // Domain_Model_Meta_Abstract type if (empty($this->_config['meta'])) { throw $this->_exception( 'ERR_MISSING_CONFIG_VALUE', array('meta') ); } elseif (!($this->_config['meta'] instanceof Domain51_Model_Meta_Abstract)) { throw $this->_exception( 'ERR_WRONG_CONFIG_TYPE', array('meta') ); } // a good meta object was passed in $this->_meta = $this->_config['meta']; // populate _data based on meta object foreach ($this->_meta->columns as $value) { if (isset($config['properties'][$value])) { $this->_data[$value] = $config['properties'][$value]; } } } /** * * Return the value of a given key. * * Since Model's keep track of changes without committing to persistant * storage, it is possible for this value to be out of sync with the * persistant storage layer. To see if this is a modified value, use * {@link isModified()} and specify the key * * This is a PHP magic method and is called directly by invoking: * $model->key; * * @param string $key * * @return void * */ public function __get($key) { if (isset($this->_changes[$key])) { // return latest changed version of this return $this->_changes[$key]; } elseif (isset($this->_data[$key])) { // return property as set at construct return $this->_data[$key]; } // nothing returned return null; } /** * * Sets a given property to a specific value. * * Throws an exception when the specified $key is unknown. * * This is a PHP magic method, and is called directly by invoking: * $model->key = "value"; * * * @param string $key * * @param mixed $value * * @return void * */ public function __set($key, $value) { if (!in_array($key, $this->_meta->columns)) { throw $this->_exception('ERR_UNKNOWN_PROPERTY', array($key)); } $this->_changes[$key] = $value; } /** * * Returns true if a given property is set * * This is a PHP magic method and is called directly by invoking: * isset($model->key); * * * @param string $key * * @return bool * */ public function __isset($key) { return isset($this->_data[$key]) || isset($this->_changes[$key]); } /** * * Unset a changed property * * This will not unset properties from persistent storages. To change the * type, you will need to actually set it to the type you wish to use. * * This is a PHP magic method and is called directly by invoking: * unset($model->key); * * * @param string $key * * @return void * */ public function __unset($key) { unset($this->_changes[$key]); } /** * * Revert all changes to this property * * * @return void * */ public function revert() { $this->_changes = array(); } /** * * Returns an array of the latest data * * * @return array * */ public function toArray() { return array_merge($this->_data, $this->_changes); } /** * * Return true if this Model has been modified. If $key is provided, * returns true only if that property has been modified. * * * @param string|null $key Specify which property to check if it is * modified. Optional. * * @return bool * */ public function isModified($key = null) { if (empty($key)) { return (count($this->_changes) > 0); } else { return isset($this->_changes[$key]); } } /** * * Return true if this Model is new * * * @return bool * */ public function isNew() { return (count($this->_data) == 0); } /** * * Attaches a valid PHP callback to be invoked on {@link save()} * * Only one callback can be attached at a time. See * {@link http://www.php.net/manual/en/function.is-callable.php is_callable()} * for a definition of what a valid callback is. * * * @param mixed $callback A valid PHP callback. * * @return void * */ public function attach($callback) { if (!is_callable($callback)) { throw $this->_exception( 'ERR_INVALID_CALLBACK', array($callback) ); } $this->_callback = $callback; } /** * * Saves this Model. * * If a callback has been specified by {@link attach()}, that callback is * invoked with this object as the only parameter. * * On save(), all changed data is persisted, and unset() will no longer * revert changes. * * * @return void * */ public function save() { if (!$this->isModified()) { return; } $this->_changes = array_merge( $this->_changes, (array)call_user_func($this->_callback, $this) ); $this->_data = array_merge($this->_data, $this->_changes); $this->_changes = array(); } } ?>