Source of file DefaultSerializer.php
Size: 13,287 Bytes - Last Modified: 2021-12-24T06:41:16+00:00
/var/www/docs.ssmods.com/process/src/src/Serializers/DefaultSerializer.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 | <?php namespace Colymba\RESTfulAPI\Serializers; use Colymba\RESTfulAPI\RESTfulAPI; use Colymba\RESTfulAPI\RESTfulAPIError; use SilverStripe\ORM\RelationList; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataList; use SilverStripe\Core\Config\Config; /** * Default RESTfulAPI Model Serializer * handles DataObject, DataList etc.. JSON serialization and de-serialization * * @author Thierry Francois @colymba thierry@colymba.com * @copyright Copyright (c) 2013, Thierry Francois * * @license http://opensource.org/licenses/BSD-3-Clause BSD Simplified * * @package RESTfulAPI * @subpackage Serializer */ class DefaultSerializer implements Serializer { /** * Content-type header definition for this Serializer * Used by RESTfulAPI in the response * * @var string */ private $contentType = 'application/json; charset=utf-8'; /** * Return Content-type header definition * to be used in the API response * * @return string Content-type */ public function getcontentType() { return $this->contentType; } /** * Stores the current $embedded_records @config * Config set on {@link RESTfulAPI} * * @var array */ protected $embeddedRecords; /** * Construct and set current config */ public function __construct() { $embedded_records = Config::inst()->get('colymba\\RESTfulAPI\\RESTfulAPI', 'embedded_records'); if (is_array($embedded_records)) { $this->embeddedRecords = $embedded_records; } else { $this->embeddedRecords = array(); } } /** * Convert data into a JSON string * * @param mixed $data Data to convert * @return string JSON data */ protected function jsonify($data) { // JSON_NUMERIC_CHECK removes leading zeros // which is an issue in cases like postcode e.g. 00160 // see https://bugs.php.net/bug.php?id=64695 $json = json_encode($data); //catch JSON parsing error $error = RESTfulAPIError::get_json_error(); if ($error !== false) { return new RESTfulAPIError(400, $error); } return $json; } /** * Convert raw data (DataObject or DataList) to JSON * ready to be consumed by the client API * * @param mixed $data Data to serialize * @return string JSON representation of data */ public function serialize($data) { $json = ''; $formattedData = null; if ($data instanceof DataObject) { $formattedData = $this->formatDataObject($data); } elseif ($data instanceof DataList) { $formattedData = $this->formatDataList($data); } if ($formattedData !== null) { $json = $this->jsonify($formattedData); } else { //fallback: convert non array to object then encode if (!is_array($data)) { $data = (object) $data; } $json = $this->jsonify($data); } return $json; } /** * Format a DataObject keys and values * ready to be turned into JSON * * @param DataObject $dataObject The data object to format * @return array|null The formatted array map representation of the DataObject or null * is permission denied */ protected function formatDataObject(DataObject $dataObject) { // api access control if (!RESTfulAPI::api_access_control($dataObject, 'GET')) { return null; } if (method_exists($dataObject, 'onBeforeSerialize')) { $dataObject->onBeforeSerialize(); } $dataObject->extend('onBeforeSerialize'); // setup $formattedDataObjectMap = array(); // get DataObject config $class = get_class($dataObject); $db = Config::inst()->get($class, 'db'); $has_one = Config::inst()->get($class, 'has_one'); $has_many = Config::inst()->get($class, 'has_many'); $many_many = Config::inst()->get($class, 'many_many'); $belongs_many_many = Config::inst()->get($class, 'belongs_many_many'); // Get a possibly defined list of "api_fields" for this DataObject. If defined, they will be the only fields // for this DataObject that will be returned, including related models. $apiFields = (array) Config::inst()->get($class, 'api_fields'); //$many_many_extraFields = $dataObject->many_many_extraFields(); $many_many_extraFields = $dataObject->stat('many_many_extraFields'); // setup ID (not included in $db!!) $serializedColumnName = $this->serializeColumnName('ID'); $formattedDataObjectMap[$serializedColumnName] = $dataObject->getField('ID'); // iterate over simple DB fields if (!$db) { $db = array(); } foreach ($db as $columnName => $fieldClassName) { // Check whether this field has been specified as allowed via api_fields if (!empty($apiFields) && !in_array($columnName, $apiFields)) { continue; } $serializedColumnName = $this->serializeColumnName($columnName); $formattedDataObjectMap[$serializedColumnName] = $dataObject->getField($columnName); } // iterate over has_one relations if (!$has_one) { $has_one = array(); } foreach ($has_one as $columnName => $fieldClassName) { // Skip if api_fields is set for the parent, and this column is not in it if (!empty($apiFields) && !in_array($columnName, $apiFields)) { continue; } $serializedColumnName = $this->serializeColumnName($columnName); // convert foreign ID to integer $relationID = intVal($dataObject->{$columnName . 'ID'}); // skip empty relations if ($relationID === 0) { continue; } // check if this should be embedded if ($this->isEmbeddable($dataObject->ClassName, $columnName)) { // get the relation's record ready to embed $embedData = $this->getEmbedData($dataObject, $columnName); // embed the data if any if ($embedData !== null) { $formattedDataObjectMap[$serializedColumnName] = $embedData; } } else { // save foreign ID $formattedDataObjectMap[$serializedColumnName] = $relationID; } } // combine defined '_many' relations into 1 array $many_relations = array(); if (is_array($has_many)) { $many_relations = array_merge($many_relations, $has_many); } if (is_array($many_many)) { $many_relations = array_merge($many_relations, $many_many); } if (is_array($belongs_many_many)) { $many_relations = array_merge($many_relations, $belongs_many_many); } // iterate '_many' relations foreach ($many_relations as $relationName => $relationClassname) { // Skip if api_fields is set for the parent, and this column is not in it if (!empty($apiFields) && !in_array($relationName, $apiFields)) { continue; } //get the DataList for this realtion's name $dataList = $dataObject->{$relationName}(); //if there actually are objects in the relation if ($dataList->count()) { // check if this relation should be embedded if ($this->isEmbeddable($dataObject->ClassName, $relationName)) { // get the relation's record(s) ready to embed $embedData = $this->getEmbedData($dataObject, $relationName); // embed the data if any if ($embedData !== null) { $serializedColumnName = $this->serializeColumnName($relationName); $formattedDataObjectMap[$serializedColumnName] = $embedData; } } else { // set column value to ID list $idList = $dataList->map('ID', 'ID')->keys(); $serializedColumnName = $this->serializeColumnName($relationName); $formattedDataObjectMap[$serializedColumnName] = $idList; } } } if ($many_many_extraFields) { $extraFieldsData = array(); // loop through extra fields config foreach ($many_many_extraFields as $relation => $fields) { $manyManyDataObjects = $dataObject->$relation(); $relationData = array(); // get the extra data for each object in the relation foreach ($manyManyDataObjects as $manyManyDataObject) { $data = $manyManyDataObjects->getExtraData($relation, $manyManyDataObject->ID); // format data foreach ($data as $key => $value) { // clear empty data if (!$value) { unset($data[$key]); continue; } $newKey = $this->serializeColumnName($key); if ($newKey != $key) { unset($data[$key]); $data[$newKey] = $value; } } // store if there is any real data if ($data) { $relationData[$manyManyDataObject->ID] = $data; } } // add individual DO extra data to the relation's extra data if ($relationData) { $key = $this->serializeColumnName($relation); $extraFieldsData[$key] = $relationData; } } // save the extrafields data if ($extraFieldsData) { $key = $this->serializeColumnName('ManyManyExtraFields'); $formattedDataObjectMap[$key] = $extraFieldsData; } } if (method_exists($dataObject, 'onAfterSerialize')) { $dataObject->onAfterSerialize($formattedDataObjectMap); } $dataObject->extend('onAfterSerialize', $formattedDataObjectMap); return $formattedDataObjectMap; } /** * Format a DataList into a formatted array ready to be turned into JSON * * @param DataList $dataList The DataList to format * @return array The formatted array representation of the DataList */ protected function formatDataList(DataList $dataList) { $formattedDataListMap = array(); foreach ($dataList as $dataObject) { $formattedDataObjectMap = $this->formatDataObject($dataObject); if ($formattedDataObjectMap) { array_push($formattedDataListMap, $formattedDataObjectMap); } } return $formattedDataListMap; } /** * Format a SilverStripe ClassName or Field name to be used by the client API * * @param string $name ClassName of DBField name * @return string Formatted name */ public function formatName($name) { return $name; } /** * Format a DB Column name or Field name to be used by the client API * * @param string $name Field name * @return string Formatted name */ protected function serializeColumnName($name) { return $name; } /** * Returns a DataObject relation's data formatted and ready to embed. * * @param DataObject $record The DataObject to get the data from * @param string $relationName The name of the relation * @return array|null Formatted DataObject or RelationList ready to embed or null if nothing to embed */ protected function getEmbedData(DataObject $record, $relationName) { if ($record->hasMethod($relationName)) { $relationData = $record->$relationName(); if ($relationData instanceof RelationList) { return $this->formatDataList($relationData); } else { return $this->formatDataObject($relationData); } } return null; } /** * Checks if a speicific model's relation should have its records embedded. * * @param string $model Model's classname * @param string $relation Relation name * @return boolean Trus if the relation should be embedded */ protected function isEmbeddable($model, $relation) { if (array_key_exists($model, $this->embeddedRecords)) { return is_array($this->embeddedRecords[$model]) && in_array($relation, $this->embeddedRecords[$model]); } return false; } } |