Model.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • library/
  • P4Cms/
  • Model.php
  • View
  • Commits
  • Open Download .zip Download (15 KB)
<?php
/**
 * Provides a base implementation for data models that have
 * key/value pairs (fields). Each sub-class may define an initial
 * set of fields via the $_fields variable.
 *
 * Each field is defined by a name and a list of field properties
 * as key/value pairs. For example:
 *
 *      protected static $_fields = array(
 *          'foo' => 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     <release>/<patch>
 */
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:
     *
     *    <field name> => <array with field properties>
     *
     * 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;
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/library/P4Cms/Model.php
#1 8972 Matt Attaway Initial add of the Chronicle source code