array( * 'property1' => 'value1', * 'property2' => 'value2' * ) * ) * * If field has no properties, it can be defined in a shorter way * just by specifying field's name: * * protected static $_fields = array('foo') * * Both methods shown above can be arbitrarily combined when * defining fields via $_fields variable. * * Field's property key must be a string where the following keys * are recognized: * * 'accessor' - value (string) specifies a name of the model's * method that will be used to retrieve field's * value; * accessor methods take no parameters and must * return a value * * 'mutator' - value (string) specifies a name of the model's * method that will be used to set field's value; * mutator methods must accept a single value * parameter and it is recommended that mutator * methods return $this to provide a fluent interface * * 'default' - value specifies default field's value (null * will be used if this property is not set) * * 'readOnly' - value (boolean) specifies if field is read only * * @copyright 2011 Perforce Software. All rights reserved. * @license Please see LICENSE.txt in top-level folder of this distribution. * @version / */ class P4Cms_Model implements P4Cms_ModelInterface { protected $_values = array(); protected $_id = null; protected static $_fields = array(); protected static $_idField = null; /** * Create a new model instance and (optionally) set the field values. * * @param array $values associative array of keyed field values * to load into the model. */ public function __construct($values = null) { // normalize fields definition $this->_normalizeFields(); if (is_array($values)) { $this->setValues($values); } } /** * Creates and returns a new instance of this class. * Useful for working around PHP's lack of chaining off 'new'. * * @param array $values associative array of keyed field * values to load into the model. * @return P4Cms_Model new instance of this model class. */ public static function create($values = null) { return new static($values); } /** * Permits field values to be accessed like public class members. * Is invoked when reading data from inaccessible members. * * @param string $field the name of the field to get the value of. * @return mixed the value of the field. */ public function __get($field) { if ($this->hasField($field)) { return $this->getValue($field); } return null; } /** * Permits field values to be set like public class members. * Is invoked when writing data to inaccessible members. * * @param string $field the name of the field to set the value of. * @param mixed $value the value to set. */ public function __set($field, $value) { if ($this->hasField($field)) { $this->setValue($field, $value); } else { $this->{$field} = $value; } } /** * Allows isset() to be called on fields. * Is invoked when calling isset() or empty() on inaccessible members. * * @param string $field the name of the field to call isset() on. */ public function __isset($field) { $value = $this->getValue($field); return isset($value); } /** * Allows unset() to be called on fields. * Is invoked when calling unset() is used on inaccessible members. * * @param string $field the name of the field to call unset() on. */ public function __unset($field) { $this->unsetValue($field); } /** * Get the id of this record. * * @return mixed the value of the id field. */ public function getId() { // if _idField is defined, return value of id field or null if unset. if (!empty(static::$_idField)) { if (array_key_exists(static::$_idField, $this->_values)) { return $this->_values[static::$_idField]; } else { return null; } } // if we make it this far; no _idField is defined, use _id value return $this->_id; } /** * Set the id of this record. * * @param mixed $id the value of the id of this record. * @return P4Cms_Model provides a fluent interface */ public function setId($id) { // if _idField is defined, set value of id field. if (isset(static::$_idField)) { if ($this->isReadOnlyField(static::$_idField)) { throw new P4Cms_Model_Exception("Can't set the value of a read-only field"); } $this->_values[static::$_idField] = $id; } else { $this->_id = $id; } return $this; } /** * Determine if this record has an id. * * @return bool true if the model has a non-empty id. */ public function hasId() { return (bool) strlen($this->getId()); } /** * Check if this model has a specific field. * * @param string $field the field to check for the existence of. * @return boolean true if the model has the named field, false otherwise. */ public function hasField($field) { return in_array($field, $this->getFields()); } /** * Check if the specified field is read only. * * @param string $field The field name to check * @return bool true if read only false otherwise */ public function isReadOnlyField($field) { return (bool)$this->_getFieldProperty($field, 'readOnly'); } /** * Get all of the model field names. * * @return array a list of field names for this model. */ public function getFields() { $fields = array_flip($this->getDefinedFields()) + $this->_values; // return field names. return array_keys($fields); } /** * Get the explicitly defined fields. * * @return array a list of this model's predefined field names */ public function getDefinedFields() { $fields = array_keys(static::$_fields); // ensure id field is present if defined. if (!empty(static::$_idField) && !in_array(static::$_idField, $fields)) { array_unshift($fields, static::$_idField); } // return field names. return $fields; } /** * Get all of the model field values. * * @return array an associative array of field values. */ public function getValues() { $values = array(); foreach ($this->getFields() as $field) { $values[$field] = $this->getValue($field); } return $values; } /** * Set all of the model's values at once. * * @param array|null $values associative array of field values or null to clear values. * @param bool $filter optional - if true, ignores values for unknown fields. * @return P4Cms_Model provides a fluent interface */ public function setValues($values, $filter = false) { if ($values === null) { $this->_values = array(); } if (!is_array($values)) { throw new InvalidArgumentException( "Cannot set values. Values must be an array." ); } foreach ($values as $field => $value) { // skip unknown fields if filter is set. if ($filter === true && !$this->hasField($field)) { continue; } // skip read only fields if ($this->isReadOnlyField($field)) { continue; } $this->setValue($field, $value); } return $this; } /** * Get a particular field value. Will route through custom * field accessor if one is defined. * * @param string $field the name of the field to get the value of. * @return mixed the value of the field. * @throws P4Cms_Model_Exception if the field does not exist. */ public function getValue($field) { // if an accessor is specified for this field, use it. $fieldAccessor = $this->_getFieldProperty($field, 'accessor'); if ($fieldAccessor !== null) { return $this->{$fieldAccessor}(); } else { return $this->_getValue($field); } } /** * Set a particular field value. Will route through custom * field mutator if one is defined. * * @param string $field the name of the field to set the value of. * @param mixed $value the value to set in the field. * @return P4Cms_Model provides a fluent interface * @throws P4Cms_Model_Exception if the field does not exist. */ public function setValue($field, $value) { // if a mutator is specified for this field, use it. $fieldMutator = $this->_getFieldProperty($field, 'mutator'); if ($fieldMutator !== null) { return $this->{$fieldMutator}($value); } else { return $this->_setValue($field, $value); } } /** * Unset a particular field value. Will route through custom * field mutator if one is defined. This will remove the field * from the fields list (@see getFields()), unless it is a 'defined' * field (@see getDefinedFields()). * * @param string $field the name of the field to unset the value of. * @return P4Cms_Model provides a fluent interface * @throws P4Cms_Model_Exception if the field does not exist. */ public function unsetValue($field) { $this->setValue($field, null); unset($this->_values[$field]); } /** * Get the model as an array. * * @return array the model data as an array. */ public function toArray() { return $this->getValues(); } /** * Generate a new model instance from an array. * * @param array $data the data to populate the model with. * @return P4Cms_Model a new model instance with the given data. */ public function fromArray($data) { $model = new static; return $model->setValues($data); } /** * Normalize fields definition such that after this operation, all fields * are defined as: * * => * * where properties array is blank for fields with no properties. */ protected function _normalizeFields() { $normalizedFields = array(); foreach (static::$_fields as $key => $value) { if (is_int($key) && is_string($value)) { $normalizedFields[$value] = array(); } else { $normalizedFields[$key] = $value; } } static::$_fields = $normalizedFields; } /** * Get value of given property from given field definition. * * @param string $field name of field to get property value for. * @param string $property name of field property to get value for. * @return mixed|null value for given field property or null * if property has not been set. */ protected function _getFieldProperty($field, $property) { return isset(static::$_fields[$field][$property]) ? static::$_fields[$field][$property] : null; } /** * Get a raw field value. Does not use custom accessor methods. * If idField is specified; will utilize 'getId' function. * * @param string $field the name of the field to get the value of. * @return mixed the value of the field. * @throws P4Cms_Model_Exception if the field does not exist. */ protected function _getValue($field) { // if they are asking for the idField; route through getId if (isset(static::$_idField) && $field === static::$_idField) { return $this->getId(); } // if field has an explicit value, return it. if (array_key_exists($field, $this->_values)) { return $this->_values[$field]; } return $this->_getDefaultValue($field); } /** * Get all of the raw field values. * * @return array an associative array of raw values. */ protected function _getValues() { $values = array(); foreach ($this->getFields() as $field) { $values[$field] = $this->_getValue($field); } return $values; } /** * Get a default field value. Returns null if there is no default * value for the requested field. * * @param string $field the name of the field to get the value of. * @return mixed|null the default value of the field or null if no * default value has been set. */ protected function _getDefaultValue($field) { return $this->_getFieldProperty($field, 'default'); } /** * Set raw field value. Does not use custom mutator methods. * If idField is specified; will utilize 'setId' function. * * @param string $field the name of the field to set the value of. * @param mixed $value the value to set in the field. * @return P4Cms_Model provides fluent interface. * @throws P4Cms_Model_Exception if the field does not exist. */ protected function _setValue($field, $value) { if ($this->isReadOnlyField($field)) { throw new P4Cms_Model_Exception("Can't set the value of a read-only field"); } // if they are setting the idField; route through setId if (isset(static::$_idField) && $field === static::$_idField) { return $this->setId($value); } $this->_values[$field] = $value; return $this; } /** * Set all of the model's raw values at once. * Does not use custom mutator methods. * * @param array $values associative array of field values. * @return P4Cms_Model provides a fluent interface */ protected function _setValues(array $values) { foreach ($values as $field => $value) { try { $this->_setValue($field, $value); } catch (P4Cms_Model_Exception $e) { } } return $this; } }