Version: current

      1 <?php
      2 /**
      3  * Apply this interface to any {@link DBField} that doesn't have a 1-1 mapping with a database field.
      4  * This includes multi-value fields and transformed fields
      5  *
      6  * @todo Unittests for loading and saving composite values (see GIS module for existing similiar unittests)
      7  *
      8  * Example with a combined street name and number:
      9  * <code>
     10 * class Street extends DBFields implements CompositeDBField() {
     11 *   protected $streetNumber;
     12 *   protected $streetName;
     13 *   protected $isChanged = false;
     14 *   static $composite_db = return array(
     15 *       "Number" => "Int",
     16 *       "Name" => "Text"
     17 *   );
     18 *
     19 *   function requireField() {
     20 *       DB::requireField($this->tableName, "{$this->name}Number", 'Int');
     21 *       DB::requireField($this->tableName, "{$this->name}Name", 'Text');
     22 *   }
     23 *
     24 *   function writeToManipulation(&$manipulation) {
     25 *       if($this->getStreetName()) {
     26 *           $manipulation['fields']["{$this->name}Name"] = $this->prepValueForDB($this->getStreetName());
     27 *       } else {
     28 *           $manipulation['fields']["{$this->name}Name"] = DBField::create('Varchar', $this->getStreetName())->nullValue();
     29 *       }
     30 *
     31 *       if($this->getStreetNumber()) {
     32 *           $manipulation['fields']["{$this->name}Number"] = $this->prepValueForDB($this->getStreetNumber());
     33 *       } else {
     34 *           $manipulation['fields']["{$this->name}Number"] = DBField::create('Int', $this->getStreetNumber())->nullValue();
     35 *       }
     36 *   }
     37 *
     38 *   function addToQuery(&$query) {
     39 *       parent::addToQuery($query);
     40 *       $query->select[] = "{$this->name}Number";
     41 *       $query->select[] = "{$this->name}Name";
     42 *   }
     43 *
     44 *   function setValue($value, $record = null, $markChanged=true) {
     45 *       if ($value instanceof Street && $value->hasValue()) {
     46 *           $this->setStreetName($value->getStreetName(), $markChanged);
     47 *           $this->setStreetNumber($value->getStreetNumber(), $markChanged);
     48 *           if($markChanged) $this->isChanged = true;
     49 *       } else if($record && isset($record[$this->name . 'Name']) && isset($record[$this->name . 'Number'])) {
     50 *           if($record[$this->name . 'Name'] && $record[$this->name . 'Number']) {
     51 *               $this->setStreetName($record[$this->name . 'Name'], $markChanged);
     52 *               $this->setStreetNumber($record[$this->name . 'Number'], $markChanged);
     53 *           }
     54 *           if($markChanged) $this->isChanged = true;
     55 *       } else if (is_array($value)) {
     56 *           if (array_key_exists('Name', $value)) {
     57 *               $this->setStreetName($value['Name'], $markChanged);
     58 *           }
     59 *           if (array_key_exists('Number', $value)) {
     60 *               $this->setStreetNumber($value['Number'], $markChanged);
     61 *           }
     62 *           if($markChanged) $this->isChanged = true;
     63 *       }
     64 *   }
     65 *
     66 *   function setStreetNumber($val, $markChanged=true) {
     67 *       $this->streetNumber = $val;
     68 *       if($markChanged) $this->isChanged = true;
     69 *   }
     70 *
     71 *   function setStreetName($val, $markChanged=true) {
     72 *       $this->streetName = $val;
     73 *       if($markChanged) $this->isChanged = true;
     74 *   }
     75 *
     76 *   function getStreetNumber() {
     77 *       return $this->streetNumber;
     78 *   }
     79 *
     80 *   function getStreetName() {
     81 *       return $this->streetName;
     82 *   }
     83 *
     84 *   function isChanged() {
     85 *       return $this->isChanged;
     86 *   }
     87 *
     88 *   function hasValue() {
     89 *       return ($this->getStreetName() || $this->getStreetNumber());
     90 *   }
     91 * }
     92  * </code>
     93  *
     94  * @package sapphire
     95  * @subpackage model
     96  */
     97 interface CompositeDBField {
     98
     99     /**
    100      * Similiar to {@link DataObject::$db},
    101      * holds an array of composite field names.
    102      * Don't include the fields "main name",
    103      * it will be prefixed in {@link requireField()}.
    104      *
    105      * @var array $composite_db
    106      */
    107     //static $composite_db;
    108
    109     /**
    110      * Set the value of this field in various formats.
    111      * Used by {@link DataObject->getField()}, {@link DataObject->setCastedField()}
    112      * {@link DataObject->dbObject()} and {@link DataObject->write()}.
    113      *
    114      * As this method is used both for initializing the field after construction,
    115      * and actually changing its values, it needs a {@link $markChanged}
    116      * parameter.
    117      *
    118      * @param DBField|array $value
    119      * @param array $record Map of values loaded from the database
    120      * @param boolean $markChanged Indicate wether this field should be marked changed.
    121      *  Set to FALSE if you are initializing this field after construction, rather
    122      *  than setting a new value.
    123      */
    124     function setValue($value, $record = null, $markChanged = true);
    125
    126     /**
    127      * Used in constructing the database schema.
    128      * Add any custom properties defined in {@link $composite_db}.
    129      * Should make one or more calls to {@link DB::requireField()}.
    130      */
    131     //abstract function requireField();
    132
    133     /**
    134      * Add the custom internal values to an INSERT or UPDATE
    135      * request passed through the ORM with {@link DataObject->write()}.
    136      * Fields are added in $manipulation['fields']. Please ensure
    137      * these fields are escaped for database insertion, as no
    138      * further processing happens before running the query.
    139      * Use {@link DBField->prepValueForDB()}.
    140      * Ensure to write NULL or empty values as well to allow
    141      * unsetting a previously set field. Use {@link DBField->nullValue()}
    142      * for the appropriate type.
    143      *
    144      * @param array $manipulation
    145      */
    146     function writeToManipulation(&$manipulation);
    147
    148     /**
    149      * Add all columns which are defined through {@link requireField()}
    150      * and {@link $composite_db}, or any additional SQL that is required
    151      * to get to these columns. Will mostly just write to the {@link SQLQuery->select}
    152      * array.
    153      *
    154      * @param SQLQuery $query
    155      */
    156     function addToQuery(&$query);
    157
    158     /**
    159      * Return array in the format of {@link $composite_db}.
    160      * Used by {@link DataObject->hasOwnDatabaseField()}.
    161      * @return array
    162      */
    163     function compositeDatabaseFields();
    164
    165     /**
    166      * Determines if the field has been changed since its initialization.
    167      * Most likely relies on an internal flag thats changed when calling
    168      * {@link setValue()} or any other custom setters on the object.
    169      *
    170      * @return boolean
    171      */
    172     function isChanged();
    173
    174     /**
    175      * Determines if any of the properties in this field have a value,
    176      * meaning at least one of them is not NULL.
    177      *
    178      * @return boolean
    179      */
    180     function hasValue();
    181
    182 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -