Konzept
Classes
- DataElement
- Restful-Server
- RestApiDataObjectListExtension
- RestApiExtension
- BaseDataFormatter
- DataFormatter
Eines der Grund-Prinzipien der API ist die Betrachtung der Objekte aus unterschiedlichen Winkeln/Kontexten, die eine eigene Wirkung im Objekt haben.
So kann jedes Objekt mit den benötigten Kontexten ausgestattet werden, die weit mehr sind als nur Create, Read, Update, Delete (CRUD). Innerhalb dieser "Grund-Operationen" bieten die Kontexte vielfältige Möglichkeiten, z.B. die unterschiedliche Darstellung Objektes im eingeloggtem und anonymen Zustand.
Die API erweitert dafür $api_access
um verchiedene Kontexte:
$api_access = array(
'view' => array(
'Title',
'Content'
),
'view.logged_in' => array(
'Title',
'Content',
'Secret'
)
);
Die CRUD Operationen werden weiterhin durch die Silverstripe-Methoden canOperation
(canView, canEdit etc.) kontrolliert. Desweiteren können die Kontexte im gleichen Stil mit getOperationContext
(getViewContext, getEditContext etc.) verwaltet werden.
static $api_access = array(
'view' => array(
'Title'
),
'view.logged_in' => array(
'Title',
'Content'
),
'view.admin' => array(
'Title',
'Contnet',
'Secret'
)
);
public function getViewContext($member = null) {
// is admin
if (Permission::checkMember($member, 'ADMIN')) {
return 'admin';
}
// is logged in
else if ($member && $member->ID) {
return 'logged_in';
}
// return nothing. The API is going to use the default 'view' context
return false;
}
Um Daten direkt ins Template zu übergeben und sich so einen weiteren Request auf die API zu sparen, bietet die API JJ_DataElement an.
Ein JJ_DataElement
kann direkt aus einem DataObject oder DataList erstellt, oder mit rohen Daten (Array) konstruiert werden.
// your index method inside a controller
function index() {
$dataElement = new JJ_DataElement('foo', array('foo' => 'bar'));
$lists = DataList::create('TodoList');
$item = DataList::create('TodoItem')->First();
return $this->customise(array(
'DataElement' => $dataElement, // raw data
'Lists' => $lists->toDataElement(), // represents a DataList
'FirstItem' => $item->toDataElement() // represents a DataObject
));
}
Attribute prefix for API generated data elements.
<script type="application/json" id="api-{$ElementName}">
{"foo": "bar"}
</script>
Extension/Format der DataElements.
Gibt das Format des DataElements zurück.
Setzt das Format des DataElements. Normalerweise json
oder xml
.
Gibt den DataFormatter
zurück und erstellt gegebenenfalls eine neue Instance auf Basis der momentanen extension()
.
Gibt den Namen des DataElements zurück.
$dataElement = new JJ_DataElement('foo', array('foo' => 'bar'));
$dataElement->name(); // foo
Gibt den Namen inklusive $att_prefix
zurück.
$dataElement = new JJ_DataElement('foo', array('foo' => 'bar'));
$dataElement->fullName(); // api-foo
Setzt den Namen des DataElements.
Gibt die Daten in Rohform (Array|Object) zurück.
Setzt die zu formattierenden Daten.
Gibt die formatierten Daten im gewählten Format zurück.
Erzeugt ein <script>
-Tag mit Typ und ID, basiert auf Format und fullName()
, das die Daten im angegebenen Format enthält.
$dataElement = new JJ_DataElement('foo', array('foo' => 'bar'));
// Usually this will be called from the Template-Engine.
echo $dataElement->forTemplate();
<script type="application/json" id="api-foo">
{"foo":"bar"}
</script>
Standard Extension falls kein Formatter gefunden werden kann.
Um Daten über die API zu publizieren (read-only), die an keine konkreten Modelle gebunden sind, z.B. den eingeloggten User, Basis-Daten (SiteConfig, CSS, Templates) etc. können die folgenden Methoden verwendet werden:
JJ_RestfulServer::add_fields('module-key', array(
'module property'
));
Löscht einzelne Felder des Keys, oder den gesammten Key, wenn keine Felder angegeben sind.
gibt alle Felder des Keys zurück. Falls kein Key angegeben wurde, alle Keys + Felder
Formatiert den Request im angegebenen Format und verwendet URL-Extension sowie Accept-Header zur Ermittlung.
Gibt den aktuellen ResponseFormatter
zurück. Falls noch keiner vorhanden ist, wird dieser aus URL-Extension und Accept-Header ermittelt.
Setzt den ResponseFormatter
anhand der mitgegebenen Extension oder via Accept-Header.
Gibt den ResponseFormatter
einer bestimmten Extension oder false
zurück falls keiner gefunden wurde.
Fügt den Content-Type
des Formatters als Header zum Response hinzu.
Die JJ_RestApiDataObjectListExtension erweitert die Funktionalität von DataObject
und DataList
um einfacher mit der API zu kommunizieren.
Felder, die unabhängig vom Kontext, in die Repräsentation des Objektes integriert werden sollen.
Standard-Kontext für eingeloggte Benutzer. Falls der Kontext nicht definiert wurde, wird der Standard-Kontext verwendet.
Die Standard-Formatierung, falls nicht über die /api/v2/
auf den Formatter zugegriffen wird.
Gibt den aktuellen DataFormatter
der API zurück. Dieser unterscheidet sich je nach URL-Extension oder $api_extension
Gibt das formatierte Objekt zurück. Ist ein Shortcut zu DataFormatter->convert
Konvertiert die mitgegebenen Felder (dot-Notation -> $api_access) in die einzelnen Objekte. Falls keine Felder mitgegeben wurden, werden die Felder von #getApiContextFields
verwendet.
Gibt das API-Kontext-Objekt zurück. Verwendet get{$Action}Context()
und fällt auf die mitgegebene $operation
zurück.
$context = $obj->getApiContext('view');
print_r($context);
stdClass Object
(
[operation] => view
[context] => view.logged_in
)
Gibt den zu verwendenden Kontext für die angegebene Operation zurück.
Verwendet getApiContext($operation)
$context = $obj->getApiContextName('view');
echo $context; // view.logged_in
Gibt die Felder des aktuellen Kontextes zurück. Verwendet dafür getApiContext($operation)
. Zudem wird gewarnt, falls kein Kontext gefunden wurde.
Gibt die Definition der Relations zu anderen DataObjects als Array zurück.
$relationKeys = $todoItem->getRelationKeys();
print_r($relationKeys);
Array
(
[TodoList] => Array
(
[ClassName] => TodoList
[Type] => has_one
[Key] => TodoList
[ReverseKey] => TodoItems
)
[Tags] => Array
(
[ClassName] => Tag
[Type] => many_many
[Key] => Tags
[ReverseKey] => TodoItems
)
)
Gibt den Reverse-Key der Relation zurück.
class TodoList extends DataObject {
static $has_many = array(
'TodoItems' => 'TodoItem'
);
...
}
class TodoItem extends DataObject {
static $has_one = array(
'TodoList' => 'TodoList'
);
...
}
$reverseKey = $todoList->getReverseRelationKey('TodoItem', 'has_one');
echo $reverseKey; // TodoItems
Konvertiert die Dot-Syntax in multidimensionale Arrays.
Gibt die Liste oder das Objekt der Verbindung zurück.
Die API ist mit eigenen Extensions erweiterbar welche als Basis-Klasse JJ_RestApiExtension
verwenden. Die Extensions können die API um neue Endpunkte erweitern sowie Template-Variablen zu Verfügung stellen.
Die JJRestAPI kommt mit folgenden Extensions:
- Structure
- User
- Template?
Enabled Flag
Der Key ist gleichzeitig das URL-Segment, unter dem die Erweiterung in der API zu finden ist /api/v2/$template_key.json sowie der Key der Template-Variablen um die Daten via DataElement ins Template zu schreiben.
// in your extension
static $template_key = 'Foo';
// access your data via /api/v2/Foo.json
// in your template.ss
<!-- $JJ_RestApi.Foo -->
$JJ_RestApi.Foo
Extensions are read-only by default.
Um weitere Variablen an das Template zu übergeben kann diese Methode überschrieben/erweitert werden.
Gibt die Daten an das Template weiter. Standardmäßig wird ein DataElement mit dem $extension_key
als Name und der Wert von getData()
als DatenSatz genommen.
Gibt die Daten an die API zurück falls der API-Endpunkt angesteuert wurde. Verwendet dafür den DataFormatter der API und convertiert den DatenSatz von getData()
.
Erstellt und gibt eine Instanz der Erweiterung mit evtl. mitgegebener JJ_RestfulServer-Instanz zurück.
Gibt den $extension_key
der Erweiterung zurück.
Gibt den Owner (JJ_RestfulServer-Instanz) der Extension zurück.
Einfacher Basis-Handler für Abfragen über die API (to_api()
). Checkt $isReadOnly und setzt die Response-Header für Content-Type
etc.
Konvertiert den mitgegebenen Datensatz mit Hilfe des DataFormatters der JJ_RestfulServer-Instanz.
Gibt die Daten in Rohform (array|object) zurück, die dann mit convert()
konvertiert und zurückgegeben werden. $extension
zeigt dabei die momentane Extension der API an, aufgrund dessen die Daten unterschiedlich strukturiert werden können.
Falls in der Extension static $api_access = array()
definiert wurde, werden die Daten aus getData()
mit den Feldern des Kontextes konvertiert.
Die API um weitere Endpunkte/Template-Tags zu erweitern ist kinderleicht ;)
class Example_RestApiExtension extends JJ_RestApiExtension implements TemplateGlobalProvider {
public static $extension_key = 'Example';
public function getData($extension = null) {
return array(
'foo' => 'bar'
);
}
}
Um die, für die App relevante, Datenstruktur mit dem Frontend (Backbone.js) zu teilen und auf die gleichen Beziehungen zurückgreifen zu können kann die Daten-Struktur mittels der Extension geteilt werden.
Die Struktur verwendet nicht nur die angegebenen DataObjects, sondern verwendet diese als Startpunkt und fügt die DataObjects der Beziehungen automatisch hinzu.
Um bestimmte DataObjects von der Struktur auszuschließen, können diese ignoriert werden.
// Foo.php
class Foo extends DataObject {
static $has_one = array(
'Secret' => 'Secret'
);
static $many_many = array(
'Bars' => 'Bar'
);
}
// Bar.php
class Bar extends DataObject {
static $many_many = array(
'Foos' => 'Foo'
);
}
// _config.php
Structure_RestApiExtension::add('Foo'); // Bar will be added automagically
Structure_RestApiExtension::ignore('Secret'); // Secret will be ignored
// /api/v2/Structure.json
{
"Foo": [
{
"ClassName" : "Bar",
"Type" : "many_many",
"Key" : "Bars",
"ReverseKey" : "Foos"
}
],
"Bar": [
{
"ClassName" : "Foo",
"Type" : "belongs_many_many",
"Key" : "Foos",
"ReverseKey" : "Bars"
}
]
}
NOTE
Die Struktur kann natürlich direkt ins Template geschrieben werden, sodass sie schon mit dem ersten Request verfügbar ist.
// RootUrlController.ss
<body>
<!-- DataStructure -->
$JJ_RestApi.Structure
</body>
Fügte ein DataObject zu der Struktur.
Fügt ein DataObject zu den zu ignorierenden Objekten.
Gibt die DataObjects der Struktur zurück.
Gibt die ClassNames der DataObjects zurück, die in der Struktur ignoriert werden sollen.
Löscht ein DataObject von der Struktur.
Löscht ein DataObject von den zu ignorierenden Objekten.
Gibt die Struktur, optimiert für den zu verwendenden Formatter, als Array zurück.
Der BaseDataFormatter
enthält einige nützliche Methoden, die von den speziellen, an die Ausgabeformate, angepassten DataFormatters verwendet werden.
Erzeugt eine neue BaseDataFormatter-Instanz mit der dazugehörigen Erweiterung.
Gibt zurück, ob der Feldname in den Feldern vorhanden ist.
Casted den Wert des Objektes, anhand des Objekt-Typs und Kontext.
Interface der DataFormatters
, dass alle zu implementierende Methoden für spezifische/eigene Formatters enthält.
Gibt den zu verwendenden BaseDataFormatter
zurück.
/**
*
* @var object JJ_BaseDataFormatter
*/
protected $baseFormatter = null;
/**
* returns and creates the JJ_BaseDataFormatter
*
* @return JJ_BaseDataFormatter
*/
public function getBase() {
if (!$this->baseFormatter) {
$this->baseFormatter = new JJ_BaseDataFormatter($this);
}
return $this->baseFormatter;
}
Gibt die Extensions zurück, bei denen die Erweiterung verwendet werden soll.
public function supportedExtensions() {
return array(
'json'
);
}
Set priority from 0-100. If multiple formatters for the same extension exist, we select the one with highest priority.
/**
* @var int
*/
public static $priority = 60;
Gibt das konvertierte Object zurück.
Gibt die DataList
alls formatiertes Array zurück. Die DataObject
s werden mit convertDataObject
formatiert.
Gibt die konvertierte DataList
zurück und verwendet dafür getDataList
Gibt das konvertierte DataObject
zurück. Felder und Relations können mit den entsprechenden parametern eingeschränkt werden. Werden keine Felder bzw. Relations angebeben wird auf getApiFields()
zurückgegriffen.
Shortcut, der je nach daten-Typ auf die oben genannten convertDataType
zurückfällt.
Die API lässt sich mit eigenen Formattierungen erweitern. Dafür ist das Interface DataFormatter
zu verwenden. Sollte das Austauschformat XML
oder JSON
sein, können zudem die vorhandenen Formatter erweitert werden.
Das DatenVisualisierungs-Tool Gephi bietet z.B. das Laden der Daten aus einem JSON Stream
herraus. Allerdings muss dieser im Gephi-Austauschformat formatiert sein.
Wir machen unsere Erweiterung unter /api/v2/DataObject.graph
verfügbar, und implementieren die von DataFormatter
verlangten Methoden.
class JJ_GraphStreamDataFormatter extends JJ_JSONDataFormatter implements JJ_DataFormatter {
/**
* Set priority from 0-100.
* If multiple formatters for the same extension exist,
* we select the one with highest priority.
*
* Set to 60 to be higher than JSONDataFormatter, cool!
*
* @var int
*/
public static $priority = 60;
/**
* use /api/v2/DataObject.graph
*/
public function supportedExtensions() {
return array(
'graph'
);
}
...