Version: current

      1 <?php
      2 /**
      3  * A DataFormatter object handles transformation of data from Sapphire model objects to a particular output format, and vice versa.
      4  * This is most commonly used in developing RESTful APIs.
      5  *
      6  * @package sapphire
      7  * @subpackage formatters
      8  */
      9 abstract class DataFormatter extends Object {
     10
     11     /**
     12      * Set priority from 0-100.
     13      * If multiple formatters for the same extension exist,
     14      * we select the one with highest priority.
     15      *
     16      * @var int
     17      */
     18     public static $priority = 50;
     19
     20     /**
     21      * Follow relations for the {@link DataObject} instances
     22      * ($has_one, $has_many, $many_many).
     23      * Set to "0" to disable relation output.
     24      *
     25      * @todo Support more than one nesting level
     26      *
     27      * @var int
     28      */
     29     public $relationDepth = 1;
     30
     31     /**
     32      * Allows overriding of the fields which are rendered for the
     33      * processed dataobjects. By default, this includes all
     34      * fields in {@link DataObject::inheritedDatabaseFields()}.
     35      *
     36      * @var array
     37      */
     38     protected $customFields = null;
     39
     40     /**
     41      * Allows addition of fields
     42      * (e.g. custom getters on a DataObject)
     43      *
     44      * @var array
     45      */
     46     protected $customAddFields = null;
     47
     48     /**
     49      * Allows to limit or add relations.
     50      * Only use in combination with {@link $relationDepth}.
     51      * By default, all relations will be shown.
     52      *
     53      * @var array
     54      */
     55     protected $customRelations = null;
     56
     57     /**
     58      * Fields which should be expicitly excluded from the export.
     59      * Comes in handy for field-level permissions.
     60      * Will overrule both {@link $customAddFields} and {@link $customFields}
     61      *
     62      * @var array
     63      */
     64     protected $removeFields = null;
     65
     66     /**
     67      * Specifies the mimetype in which all strings
     68      * returned from the convert*() methods should be used,
     69      * e.g. "text/xml".
     70      *
     71      * @var string
     72      */
     73     protected $outputContentType = null;
     74
     75     /**
     76      * Used to set totalSize properties on the output
     77      * of {@link convertDataObjectSet()}, shows the
     78      * total number of records without the "limit" and "offset"
     79      * GET parameters. Useful to implement pagination.
     80      *
     81      * @var int
     82      */
     83     protected $totalSize;
     84
     85     /**
     86      * Get a DataFormatter object suitable for handling the given file extension.
     87      *
     88      * @param string $extension
     89      * @return DataFormatter
     90      */
     91     static function for_extension($extension) {
     92         $classes = ClassInfo::subclassesFor("DataFormatter");
     93         array_shift($classes);
     94         $sortedClasses = array();
     95         foreach($classes as $class) {
     96             $sortedClasses[$class] = singleton($class)->stat('priority');
     97         }
     98         arsort($sortedClasses);
     99         foreach($sortedClasses as $className => $priority) {
    100             $formatter = new $className();
    101             if(in_array($extension, $formatter->supportedExtensions())) {
    102                 return $formatter;
    103             }
    104         }
    105     }
    106
    107     /**
    108      * Get formatter for the first matching extension.
    109      *
    110      * @param array $extensions
    111      * @return DataFormatter
    112      */
    113     static function for_extensions($extensions) {
    114         foreach($extensions as $extension) {
    115             if($formatter = self::for_extension($extension)) return $formatter;
    116         }
    117
    118         return false;
    119     }
    120
    121     /**
    122      * Get a DataFormatter object suitable for handling the given mimetype.
    123      *
    124      * @param string $mimeType
    125      * @return DataFormatter
    126      */
    127     static function for_mimetype($mimeType) {
    128         $classes = ClassInfo::subclassesFor("DataFormatter");
    129         array_shift($classes);
    130         $sortedClasses = array();
    131         foreach($classes as $class) {
    132             $sortedClasses[$class] = singleton($class)->stat('priority');
    133         }
    134         arsort($sortedClasses);
    135         foreach($sortedClasses as $className => $priority) {
    136             $formatter = new $className();
    137             if(in_array($mimeType, $formatter->supportedMimeTypes())) {
    138                 return $formatter;
    139             }
    140         }
    141     }
    142
    143     /**
    144      * Get formatter for the first matching mimetype.
    145      * Useful for HTTP Accept headers which can contain
    146      * multiple comma-separated mimetypes.
    147      *
    148      * @param array $mimetypes
    149      * @return DataFormatter
    150      */
    151     static function for_mimetypes($mimetypes) {
    152         foreach($mimetypes as $mimetype) {
    153             if($formatter = self::for_mimetype($mimetype)) return $formatter;
    154         }
    155
    156         return false;
    157     }
    158
    159     /**
    160      * @param array $fields
    161      */
    162     public function setCustomFields($fields) {
    163         $this->customFields = $fields;
    164     }
    165
    166     /**
    167      * @return array
    168      */
    169     public function getCustomFields() {
    170         return $this->customFields;
    171     }
    172
    173     /**
    174      * @param array $fields
    175      */
    176     public function setCustomAddFields($fields) {
    177         $this->customAddFields = $fields;
    178     }
    179
    180     /**
    181      * @param array $relations
    182      */
    183     public function setCustomRelations($relations) {
    184         $this->customRelations = $relations;
    185     }
    186
    187     /**
    188      * @return array
    189      */
    190     public function getCustomRelations() {
    191         return $this->customRelations;
    192     }
    193
    194     /**
    195      * @return array
    196      */
    197     public function getCustomAddFields() {
    198         return $this->customAddFields;
    199     }
    200
    201     /**
    202      * @param array $fields
    203      */
    204     public function setRemoveFields($fields) {
    205         $this->removeFields = $fields;
    206     }
    207
    208     /**
    209      * @return array
    210      */
    211     public function getRemoveFields() {
    212         return $this->removeFields;
    213     }
    214
    215     public function getOutputContentType() {
    216         return $this->outputContentType;
    217     }
    218
    219     /**
    220      * @param int $size
    221      */
    222     public function setTotalSize($size) {
    223         $this->totalSize = (int)$size;
    224     }
    225
    226     /**
    227      * @return int
    228      */
    229     public function getTotalSize() {
    230         return $this->totalSize;
    231     }
    232
    233     /**
    234      * Returns all fields on the object which should be shown
    235      * in the output. Can be customised through {@link self::setCustomFields()}.
    236      *
    237      * @todo Allow for custom getters on the processed object (currently filtered through inheritedDatabaseFields)
    238      * @todo Field level permission checks
    239      *
    240      * @param DataObject $obj
    241      * @return array
    242      */
    243     protected function getFieldsForObj($obj) {
    244         $dbFields = array();
    245
    246         // if custom fields are specified, only select these
    247         if(is_array($this->customFields)) {
    248             foreach($this->customFields as $fieldName) {
    249                 // @todo Possible security risk by making methods accessible - implement field-level security
    250                 if($obj->hasField($fieldName) || $obj->hasMethod("get{$fieldName}")) $dbFields[$fieldName] = $fieldName;
    251             }
    252         } else {
    253             // by default, all database fields are selected
    254             $dbFields = $obj->inheritedDatabaseFields();
    255         }
    256
    257         if(is_array($this->customAddFields)) {
    258             foreach($this->customAddFields as $fieldName) {
    259                 // @todo Possible security risk by making methods accessible - implement field-level security
    260                 if($obj->hasField($fieldName) || $obj->hasMethod("get{$fieldName}")) $dbFields[$fieldName] = $fieldName;
    261             }
    262         }
    263
    264         // add default required fields
    265         $dbFields = array_merge($dbFields, array('ID'=>'Int'));
    266
    267         // @todo Requires PHP 5.1+
    268         if(is_array($this->removeFields)) {
    269             $dbFields = array_diff_key($dbFields, array_combine($this->removeFields,$this->removeFields));
    270         }
    271
    272         return $dbFields;
    273     }
    274
    275     /**
    276      * Return an array of the extensions that this data formatter supports
    277      */
    278     abstract function supportedExtensions();
    279
    280     abstract function supportedMimeTypes();
    281
    282
    283     /**
    284      * Convert a single data object to this format.  Return a string.
    285      */
    286     abstract function convertDataObject(DataObjectInterface $do);
    287
    288     /**
    289      * Convert a data object set to this format.  Return a string.
    290      */
    291     abstract function convertDataObjectSet(DataObjectSet $set);
    292
    293     /**
    294      * @param string $strData HTTP Payload as string
    295      */
    296     public function convertStringToArray($strData) {
    297         user_error('DataFormatter::convertStringToArray not implemented on subclass', E_USER_ERROR);
    298     }
    299
    300 }

[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. -