Add field mapping config

This commit is contained in:
Bernard Hamlin 2018-03-08 14:20:31 +13:00
parent b59d956143
commit aada3e350f
5 changed files with 149 additions and 8 deletions

View File

@ -59,6 +59,44 @@ class Article extends DataObject {
}
```
Example DataObject field mapping, allows aliasing fields so that public requests and responses display different field names:
```php
namespace Vendor\Project;
use SilverStripe\ORM\DataObject;
class Article extends DataObject {
private static $db = [
'Title'=>'Text',
'Published'=>'Boolean'
];
private static $api_access = [
'view' => ['Title', 'Content'],
];
private static $api_field_mapping = [
'customTitle' => 'Title',
];
}
```
Given a dataobject with values:
```yml
ID: 12
Title: Title Value
Content: Content value
```
which when requesting with the url `/api/v1/Vendor-Project-Article/12?fields=customTitle,Content` and `Accept: application/json` the response will look like:
```Javascript
{
"customTitle": "Title Value",
"Content": "Content value"
}
```
Similarly, `PUT` or `POST` requests will have fields transformed from the alias name to the DB field name.
## Supported operations
- `GET /api/v1/(ClassName)/(ID)` - gets a database record

View File

@ -356,4 +356,87 @@ abstract class DataFormatter
{
user_error('DataFormatter::convertStringToArray not implemented on subclass', E_USER_ERROR);
}
/**
* Convert an array of aliased field names to their Dataobject field name
*
* @param string $className
* @param string[] $fields
* @return string[]
*/
public function getRealFields($className, $fields)
{
$apiMapping = Config::inst()->get($className, 'api_field_mapping');
if (is_array($apiMapping) && is_array($fields)) {
$mappedFields = [];
foreach ($fields as $field) {
$mappedFields[] = $this->getMappedKey($apiMapping, $field);
}
return $mappedFields;
}
return $fields;
}
/**
* Get the DataObject field name from its alias
*
* @param string $className
* @param string $field
* @return string
*/
public function getRealFieldName($className, $field)
{
$apiMapping = $this->getApiMapping($className);
return $this->getMappedKey($apiMapping, $field);
}
/**
* Get a DataObject Field's Alias
* defaults to the fieldname
*
* @param string $className
* @param string $field
* @return string
*/
public function getFieldAlias($className, $field)
{
$apiMapping = $this->getApiMapping($className);
$apiMapping = array_flip($apiMapping);
return $this->getMappedKey($apiMapping, $field);
}
/**
* Get the 'api_field_mapping' config value for a class
* or return an empty array
*
* @param string $className
* @return string[]|array
*/
protected function getApiMapping($className)
{
$apiMapping = Config::inst()->get($className, 'api_field_mapping');
if ($apiMapping && is_array($apiMapping)) {
return $apiMapping;
}
return [];
}
/**
* Helper function to get mapped field names
*
* @param array $map
* @param string $key
* @return string
*/
protected function getMappedKey($map, $key)
{
if (is_array($map)) {
if (array_key_exists($key, $map)) {
return $map[$key];
} else {
return $key;
}
}
return $key;
}
}

View File

@ -74,7 +74,8 @@ class JSONDataFormatter extends DataFormatter
}
$fieldValue = $obj->obj($fieldName)->forTemplate();
$serobj->$fieldName = $fieldValue;
$mappedFieldName = $this->getFieldAlias($className, $fieldName);
$serobj->$mappedFieldName = $fieldValue;
}
if ($this->relationDepth > 0) {

View File

@ -82,7 +82,8 @@ class XMLDataFormatter extends DataFormatter
} else {
$fieldValue = Convert::raw2xml($fieldValue);
}
$xml .= "<$fieldName>$fieldValue</$fieldName>\n";
$mappedFieldName = $this->getFieldAlias(get_class($obj), $fieldName);
$xml .= "<$mappedFieldName>$fieldValue</$mappedFieldName>\n";
}
}

View File

@ -13,6 +13,7 @@ use SilverStripe\ORM\SS_List;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\CMS\Model\SiteTree;
use TractorCow\Fluent\Task\ConvertTranslatableTask\Exception;
/**
* Generic RESTful server, which handles webservice access to arbitrary DataObjects.
@ -257,7 +258,8 @@ class RestfulServer extends Controller
$this->getResponse()->addHeader('Content-Type', $responseFormatter->getOutputContentType());
$rawFields = $this->request->getVar('fields');
$fields = $rawFields ? explode(',', $rawFields) : null;
$realFields = $responseFormatter->getRealFields($className, explode(',', $rawFields));
$fields = $rawFields ? $realFields : null;
if ($obj instanceof SS_List) {
$objs = ArrayList::create($obj->toArray());
@ -345,10 +347,12 @@ class RestfulServer extends Controller
// set custom fields
if ($customAddFields = $this->request->getVar('add_fields')) {
$formatter->setCustomAddFields(explode(',', $customAddFields));
$customAddFields = $formatter->getRealFields($className, explode(',', $customAddFields));
$formatter->setCustomAddFields($customAddFields);
}
if ($customFields = $this->request->getVar('fields')) {
$formatter->setCustomFields(explode(',', $customFields));
$customFields = $formatter->getRealFields($className, explode(',', $customFields));
$formatter->setCustomFields($customFields);
}
$formatter->setCustomRelations($this->getAllowedRelations($className));
@ -488,6 +492,13 @@ class RestfulServer extends Controller
return $this->notFound();
}
$reqFormatter = $this->getRequestDataFormatter($className);
if (!$reqFormatter) {
return $this->unsupportedMediaType();
}
$relation = $reqFormatter->getRealFieldName($className, $relation);
if (!$obj->hasMethod($relation)) {
return $this->notFound();
}
@ -561,16 +572,23 @@ class RestfulServer extends Controller
}
if (!empty($body)) {
$data = $formatter->convertStringToArray($body);
$rawdata = $formatter->convertStringToArray($body);
} else {
// assume application/x-www-form-urlencoded which is automatically parsed by PHP
$data = $this->request->postVars();
$rawdata = $this->request->postVars();
}
$className = $this->unsanitiseClassName($this->request->param('ClassName'));
// update any aliased field names
$data = [];
foreach ($rawdata as $key => $value) {
$newkey = $formatter->getRealFieldName($className, $key);
$data[$newkey] = $value;
}
// @todo Disallow editing of certain keys in database
$data = array_diff_key($data, ['ID', 'Created']);
$className = $this->unsanitiseClassName($this->request->param('ClassName'));
$apiAccess = singleton($className)->config()->api_access;
if (is_array($apiAccess) && isset($apiAccess['edit'])) {
$data = array_intersect_key($data, array_combine($apiAccess['edit'], $apiAccess['edit']));