Skip to content

Latest commit

 

History

History

jjrestapi

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

JJRestAPI

Konzept

Classes


Kontexte

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;
	}

DataElement

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
	));
}

static $att_prefix = 'api-'

Attribute prefix for API generated data elements.

<script type="application/json" id="api-{$ElementName}">
	{"foo": "bar"}
</script>	

static $default_extension = 'json'

Extension/Format der DataElements.

__construct(string $name, $data = array(), $extension = null)

extension()

Gibt das Format des DataElements zurück.

setExtension(string $value)

Setzt das Format des DataElements. Normalerweise json oder xml.

formatter()

Gibt den DataFormatter zurück und erstellt gegebenenfalls eine neue Instance auf Basis der momentanen extension().

name()

Gibt den Namen des DataElements zurück.

$dataElement = new JJ_DataElement('foo', array('foo' => 'bar'));
$dataElement->name(); // foo

fullName()

Gibt den Namen inklusive $att_prefix zurück.

$dataElement = new JJ_DataElement('foo', array('foo' => 'bar'));
$dataElement->fullName(); // api-foo

setName(string $value)

Setzt den Namen des DataElements.

data()

Gibt die Daten in Rohform (Array|Object) zurück.

setData(array|object $value)

Setzt die zu formattierenden Daten.

formattedData()

Gibt die formatierten Daten im gewählten Format zurück.

forTemplate()

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>

Restful-Server

static $default_extension = 'json'

Standard Extension falls kein Formatter gefunden werden kann.


Non-Object Requests

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:

add_fields(string $key, array $fields)

JJ_RestfulServer::add_fields('module-key', array(
	'module property'		
));

remove_fields(string $key, $fields = array())

Löscht einzelne Felder des Keys, oder den gesammten Key, wenn keine Felder angegeben sind.

fields(string $key = '')

gibt alle Felder des Keys zurück. Falls kein Key angegeben wurde, alle Keys + Felder


ResponseFormatter

Formatiert den Request im angegebenen Format und verwendet URL-Extension sowie Accept-Header zur Ermittlung.

getResponseFormatter()

Gibt den aktuellen ResponseFormatter zurück. Falls noch keiner vorhanden ist, wird dieser aus URL-Extension und Accept-Header ermittelt.

setResponseFormatter($extension = null)

Setzt den ResponseFormatter anhand der mitgegebenen Extension oder via Accept-Header.

getDataFormatterByExtension($includeAcceptHeader = false)

Gibt den ResponseFormatter einer bestimmten Extension oder false zurück falls keiner gefunden wurde.

addContentTypeHeader()

Fügt den Content-Type des Formatters als Header zum Response hinzu.


JJ_RestApiDataObjectListExtension

Die JJ_RestApiDataObjectListExtension erweitert die Funktionalität von DataObject und DataList um einfacher mit der API zu kommunizieren.

$api_default_fields = array('ID' => 'Int')

Felder, die unabhängig vom Kontext, in die Repräsentation des Objektes integriert werden sollen.

$api_logged_in_context_name = 'logged_in'

Standard-Kontext für eingeloggte Benutzer. Falls der Kontext nicht definiert wurde, wird der Standard-Kontext verwendet.


DataFormatter

static $api_extension = 'json'

Die Standard-Formatierung, falls nicht über die /api/v2/ auf den Formatter zugegriffen wird.

ApiFormatter()

Gibt den aktuellen DataFormatter der API zurück. Dieser unterscheidet sich je nach URL-Extension oder $api_extension


API Methods

toApi()

Gibt das formatierte Objekt zurück. Ist ein Shortcut zu DataFormatter->convert

getApiFields($fields = null)

Konvertiert die mitgegebenen Felder (dot-Notation -> $api_access) in die einzelnen Objekte. Falls keine Felder mitgegeben wurden, werden die Felder von #getApiContextFields verwendet.

getApiContext($operation = 'view')

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
	)

getApiContextName($operation = 'view')

Gibt den zu verwendenden Kontext für die angegebene Operation zurück. Verwendet getApiContext($operation)

$context = $obj->getApiContextName('view');
echo $context; // view.logged_in

getApiContextFields($operation = 'view')

Gibt die Felder des aktuellen Kontextes zurück. Verwendet dafür getApiContext($operation). Zudem wird gewarnt, falls kein Kontext gefunden wurde.


getRelationKeys($component = null, $classOnly = true)

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
		)
)

getReverseRelationKey(string $className, string $key)

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

convertRelationsNames(array $fields)

Konvertiert die Dot-Syntax in multidimensionale Arrays.

getRelation(stirng $relName, string $relType)

Gibt die Liste oder das Objekt der Verbindung zurück.


JJ_RestApiExtension

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:

static $enabled = true

Enabled Flag

static $extension_key = ''

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

$isReadOnly = true

Extensions are read-only by default.

static get_template_global_variables()

Um weitere Variablen an das Template zu übergeben kann diese Methode überschrieben/erweitert werden.

static for_template()

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.

static for_api(JJ_RestfulServer $restfulServer)

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().

static create(JJ_RestfulServer $restfulServer = null)

Erstellt und gibt eine Instanz der Erweiterung mit evtl. mitgegebener JJ_RestfulServer-Instanz zurück.

static extension_key()

Gibt den $extension_key der Erweiterung zurück.

getOwner()

Gibt den Owner (JJ_RestfulServer-Instanz) der Extension zurück.

handleExtension()

Einfacher Basis-Handler für Abfragen über die API (to_api()). Checkt $isReadOnly und setzt die Response-Header für Content-Type etc.

convert(array|object $data, null|array $fields)

Konvertiert den mitgegebenen Datensatz mit Hilfe des DataFormatters der JJ_RestfulServer-Instanz.

getData($extension = null)

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.

getFields($api_access = array(), $context = null)

Falls in der Extension static $api_access = array() definiert wurde, werden die Daten aus getData() mit den Feldern des Kontextes konvertiert.

Wie schreibe ich meine eigene Erweiterung?

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'
		);
	}

}

Structure_RestApiExtension

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>

static add(string|array $className)

Fügte ein DataObject zu der Struktur.

static ignore(string|array $className)

Fügt ein DataObject zu den zu ignorierenden Objekten.

static get()

Gibt die DataObjects der Struktur zurück.

static get_ignored()

Gibt die ClassNames der DataObjects zurück, die in der Struktur ignoriert werden sollen.

static remove(string|array $className)

Löscht ein DataObject von der Struktur.

static unignore(string|array $className)

Löscht ein DataObject von den zu ignorierenden Objekten.

getData($extension = 'json')

Gibt die Struktur, optimiert für den zu verwendenden Formatter, als Array zurück.


BaseDataFormatter

Der BaseDataFormatter enthält einige nützliche Methoden, die von den speziellen, an die Ausgabeformate, angepassten DataFormatters verwendet werden.

__construct($extensionFormatter)

Erzeugt eine neue BaseDataFormatter-Instanz mit der dazugehörigen Erweiterung.

superUnique(array $array)

link

fieldFilter(string $fieldName, array $fields)

Gibt zurück, ob der Feldname in den Feldern vorhanden ist.

castFieldValue($obj, $context = '')

Casted den Wert des Objektes, anhand des Objekt-Typs und Kontext.


DataFormatter

Interface der DataFormatters, dass alle zu implementierende Methoden für spezifische/eigene Formatters enthält.

getBase()

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;
}

supportedExtensions()

Gibt die Extensions zurück, bei denen die Erweiterung verwendet werden soll.

public function supportedExtensions() {
	return array(
		'json'
	);
}

static int $priority

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;

convertObj(object $obj, array $keys = null)

Gibt das konvertierte Object zurück.

getDataList(SS_List $set, array $fields = null)

Gibt die DataList alls formatiertes Array zurück. Die DataObjects werden mit convertDataObject formatiert.

convertDataList(SS_List $list, array $fields = null)

Gibt die konvertierte DataList zurück und verwendet dafür getDataList

convertDataObject(DataObjectInterface $obj, array $fields = null, array $relations = null)

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.

convert(SS_List|DataObject|object $data, $fields = null)

Shortcut, der je nach daten-Typ auf die oben genannten convertDataType zurückfällt.

Wie erweitere ich die API um einen weiteren Formatter?

Die API lässt sich mit eigenen Formattierungen erweitern. Dafür ist das Interface DataFormatter zu verwenden. Sollte das Austauschformat XMLoder JSON sein, können zudem die vorhandenen Formatter erweitert werden.

Beispiel

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'
		);
	}
	
	...