by Jani Tarvainen, Antonin Savoie & Baptiste Leulliette
Summary
Doc is love... Doc is life !
https://github.com/ezsystems/ezpublish-kernel/blob/master/doc/specifications/rest/REST-API-V2.rst
Old school or new school?
Which side are you?!
Let’s consume the API...
What do we need here ?�
HTTP header ‘Accept’
REST prefix : /api/ezp/v2
Media types and endpoints
<Root media-type="application/vnd.ez.api.Root+xml">� <content media-type="" href="/api/ezp/v2/content/objects"/>� <contentByRemoteId media-type="" href="/api/ezp/v2/content/objects{?remoteId}"/>� <contentTypes media-type="application/vnd.ez.api.ContentTypeInfoList+xml" href="/api/ezp/v2/content/types"/>� <contentTypeByIdentifier media-type="" href="/api/ezp/v2/content/types{?identifier}"/>� <contentTypeGroups media-type="application/vnd.ez.api.ContentTypeGroupList+xml" href="/api/ezp/v2/content/typegroups"/>� <contentTypeGroupByIdentifier media-type="" href="/api/ezp/v2/content/typegroups{?identifier}"/>� <users media-type="application/vnd.ez.api.UserRefList+xml" href="/api/ezp/v2/user/users"/>� <roles media-type="application/vnd.ez.api.RoleList+xml" href="/api/ezp/v2/user/roles"/>� <rootLocation media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/2"/>� <rootUserGroup media-type="application/vnd.ez.api.UserGroup+xml" href="/api/ezp/v2/user/groups/1/5"/>� <rootMediaFolder media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/43"/>� <locationByRemoteId media-type="" href="/api/ezp/v2/content/locations{?remoteId}"/>� <locationByPath media-type="" href="/api/ezp/v2/content/locations{?locationPath}"/>� <trash media-type="application/vnd.ez.api.Trash+xml" href="/api/ezp/v2/content/trash"/>� <sections media-type="application/vnd.ez.api.SectionList+xml" href="/api/ezp/v2/content/sections"/>� <views media-type="application/vnd.ez.api.RefList+xml" href="/api/ezp/v2/views"/>� <objectStateGroups media-type="application/vnd.ez.api.ObjectStateGroupList+xml" href="/api/ezp/v2/content/objectstategroups"/>� <objectStates media-type="application/vnd.ez.api.ObjectStateList+xml" href="/api/ezp/v2/content/objectstategroups/{objectStateGroupId}/objectstates"/>� <globalUrlAliases media-type="application/vnd.ez.api.UrlAliasRefList+xml" href="/api/ezp/v2/content/urlaliases"/>� <urlWildcards media-type="application/vnd.ez.api.UrlWildcardList+xml" href="/api/ezp/v2/content/urlwildcards"/>� <createSession media-type="application/vnd.ez.api.UserSession+xml" href="/api/ezp/v2/user/sessions"/>� <refreshSession media-type="application/vnd.ez.api.UserSession+xml" href="/api/ezp/v2/user/sessions/{sessionId}/refresh"/>�</Root>
Let’s consume the API : ‘Accept’ HTTP header
application/vnd.ez.api.
[Media-Type]
+[Content-Type]
Let’s consume the REST API: Time to try!
curl ezrestapi.websc/api/ezp/v2/content/objects/1
<Content media-type="application/vnd.ez.api.ContentInfo+xml" href="/api/ezp/v2/content/objects/1" remoteId="9459d3c29e15006e45197295722c7ade" id="1">
<ContentType media-type="application/vnd.ez.api.ContentType+xml" href="/api/ezp/v2/content/types/1"/>
<Name>eZ Platform</Name>
<Versions media-type="application/vnd.ez.api.VersionList+xml" href="/api/ezp/v2/content/objects/1/versions"/>
<CurrentVersion media-type="application/vnd.ez.api.Version+xml" href="/api/ezp/v2/content/objects/1/currentversion"/>
<Section media-type="application/vnd.ez.api.Section+xml" href="/api/ezp/v2/content/sections/1"/>
<MainLocation media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/2"/>
<Locations media-type="application/vnd.ez.api.LocationList+xml" href="/api/ezp/v2/content/objects/1/locations"/>
<Owner media-type="application/vnd.ez.api.User+xml" href="/api/ezp/v2/user/users/14"/>
<lastModificationDate>2015-11-30T14:10:46+01:00</lastModificationDate>
<publishedDate>2015-11-30T14:10:46+01:00</publishedDate>
<mainLanguageCode>eng-GB</mainLanguageCode>
<currentVersionNo>9</currentVersionNo>
<alwaysAvailable>true</alwaysAvailable>
<ObjectStates media-type="application/vnd.ez.api.ContentObjectStates+xml" href="/api/ezp/v2/content/objects/1/objectstates"/>
</Content>
Let’s consume the REST API: Time to try!
curl ezrestapi.websc/api/ezp/v2/content/objects/1
-H "Accept: application/vnd.ez.api.ContentInfo+json"
"Content": {
"_media-type": "application/vnd.ez.api.ContentInfo+json",
"_href": "/api/ezp/v2/content/objects/1",
"_remoteId": "9459d3c29e15006e45197295722c7ade",
"_id": 1,
"ContentType": {
"_media-type": "application/vnd.ez.api.ContentType+json",
"_href": "/api/ezp/v2/content/types/1"
},
"Name": "eZ Platform",
"Versions": {
"_media-type": "application/vnd.ez.api.VersionList+json",
"_href": "/api/ezp/v2/content/objects/1/versions"
},
"CurrentVersion": {
"_media-type": "application/vnd.ez.api.Version+json",
"_href": "/api/ezp/v2/content/objects/1/currentversion"
},
"Section": {
"_media-type": "application/vnd.ez.api.Section+json",
"_href": "/api/ezp/v2/content/sections/1"
},
"MainLocation": {
"_media-type": "application/vnd.ez.api.Location+json",
"_href": "/api/ezp/v2/content/locations/1/2"
},
"Locations": {
"_media-type": "application/vnd.ez.api.LocationList+json",
"_href": "/api/ezp/v2/content/objects/1/locations"
},
"Owner": {
"_media-type": "application/vnd.ez.api.User+json",
"_href": "/api/ezp/v2/user/users/14"
},
"lastModificationDate": "2015-11-30T14:10:46+01:00",
"publishedDate": "2015-11-30T14:10:46+01:00",
"mainLanguageCode": "eng-GB",
"currentVersionNo": 9,
"alwaysAvailable": true
}
There is any content fields
Let’s consume the REST API: Time to try!
curl ezrestapi.websc/api/ezp/v2/content/objects/1
-H "Accept: application/vnd.ez.api.Content+json"
"CurrentVersion": {
...,
"Version": {
…,
"VersionInfo": {
"id": 506,
"versionNo": 9,
"status": "PUBLISHED",
"modificationDate": "2015-11-30T14:10:46+01:00",
"creationDate": "2015-11-30T14:10:45+01:00",
"initialLanguageCode": "eng-GB",
"languageCodes": "eng-GB",
"Content": {
"_media-type": "application/vnd.ez.api.ContentInfo+json",
"_href": "/api/ezp/v2/content/objects/1"
}
},
"Fields": {
"field": [
{
"id": 1,
"fieldDefinitionIdentifier": "name",
"languageCode": "eng-GB",
"fieldValue": "Welcome to eZ Platform"
},
Here we go !
We have our fields !
Install the fixtures
php app/console restapi:generate-fixtures
git fetch
git checkout exercice-extend-API
Extend the Rest API : Register the endpoint
�
custom_rest_routes:� resource: "@AppBundle/Resources/config/routing_rest.yml"� prefix: "%ezpublish_rest.path_prefix%"
search_by_category:� path: /search/category/{categoryName}� defaults: { _controller: AppBundle:API:searchByCategory }� methods: [GET]
Extend the Rest API : Representation of our Session
Let’s create a class that will represent
our eZ Content
�
class Session�{� public $title;� public $description;� public $category;� public $protagonist;� public $startDate;� public $endDate;� public $_contentId;� public $_locationId;�� public function __construct(Content $content)� {� $this->title = $content->getFieldValue('title')->text;� $this->description = $content->getFieldValue('description')->text;� $this->category = $content->getFieldValue('category')->text;� $this->protagonist = $content->getFieldValue('protagonist')->text;� $this->startDate = $content->getFieldValue('startDate')->value;� $this->endDate = $content->getFieldValue('endDate')->value;�� $this->_contentId = $content->id;� $this->_locationId = $content->contentInfo->mainLocationId;� }�}
Extend the Rest API : Performs the search
� $searchService = $this->container->get('ezpublish.api.repository')->getSearchService();�� $query = new Query(� array(� 'filter' => new LogicalAnd(� array(� new ContentTypeIdentifier( 'session' ),� new Field( 'category', Criterion\Operator::EQ, $categoryName ),� )� )� )� );�� try {� $searchResult= $searchService->findContent($query);� $items = [];�� foreach ($searchResult->searchHits as $item) {� $items[] = new Session($item->valueObject);� }�� return new JsonResponse($items);� } catch (InvalidArgumentException $e) {� throw $e;� }�
curl ezrestapi.websc/api/ezp/v2/search/category/ez
[
{
"title": "Real-life use cases leveraging Solr power",
"description": "",
"category": "ez",
"protagonist": "Petar Španja (Netgen)",
"startDate": {
"date": "2017-08-30 07:30:00.000000",
"timezone_type": 1,
"timezone": "+00:00"
},
"endDate": {
"date": "2017-08-30 10:45:00.000000",
"timezone_type": 1,
"timezone": "+00:00"
},
"_contentId": 578,
"_locationId": 575
},
{
...
}
]
Extend the Rest API : Wait….
Extend the Rest API : let’s do it properly
namespace AppBundle\Rest\values;
class SessionList�{� public $sessions;�� public function __construct( $sessions )� {� $this->sessions = $sessions;� }�}
Create a SessionList class that represent a list our sessions
Extend the Rest API : let’s do it properly
� $searchService = $this->container->get('ezpublish.api.repository')->getSearchService();�� $query = new Query(� array(� 'filter' => new LogicalAnd(� array(� new ContentTypeIdentifier( 'session' ),� new Field( 'category', Criterion\Operator::EQ, $categoryName ),� )� )� )� );�� try {� $searchResult= $searchService->findContent($query);� $items = [];�� foreach ($searchResult->searchHits as $item) {� $items[] = new Session($item->valueObject);� }�� return new SessionList($items);� } catch (InvalidArgumentException $e) {� throw $e;� }�
Change the response returned by the controller ��We go from JsonResponse to SessionList
Extend the Rest API : Register a visitor
app.rest.value_object_visitor.session_list:� parent: ezpublish_rest.output.value_object_visitor.base� class: AppBundle\Rest\valueObjectVisitor\SessionList� tags:� - { name: ezpublish_rest.output.value_object_visitor, type: AppBundle\Rest\values\SessionList }
First, register your visitor in app/config/services.yml using a specific tag name
Let’s try again our endpoit
Extend the Rest API : Register a visitor
namespace AppBundle\Rest\valueObjectVisitor;���use AppBundle\Rest\values\Session;�use eZ\Publish\Core\REST\Common\Output\ValueObjectVisitor;�use eZ\Publish\Core\REST\Common\Output\Generator;�use eZ\Publish\Core\REST\Common\Output\Visitor;��class SessionList extends ValueObjectVisitor�{� public function visit( Visitor $visitor, Generator $generator, $data )� {� // Your visitor will build the output format of the response� }�}
Second, write the visitor
$generator->startObjectElement('SessionList');� $generator->startList('Sessions');�
/** @var Session $session */� foreach ($data->sessions as $session) {� $generator->startObjectElement('Session');� $generator->startValueElement('title', $session->title);� $generator->endValueElement('title');�� $generator->startValueElement('description', $session->description);� $generator->endValueElement('description');�� ….� $generator->endObjectElement('Session');� }�� $generator->endList('Sessions');� $generator->endObjectElement('SessionList');
In our example
Extend the Rest API : Try again
curl ezrestapi.websc/api/ezp/v2/search/category/ez
<SessionList media-type="application/vnd.ez.api.SessionList+xml">
<Session media-type="application/vnd.ez.api.Session+xml">
<title>Real-life use cases leveraging Solr power</title>
<description></description>
<category>ez</category>
<protagonist>Petar Španja (Netgen)</protagonist>
<startDate>1504078200</startDate>
<endDate>1504089900</endDate>
<_contentId>578</_contentId>
<_locationId>575</_locationId>
</Session>
<Session media-type="application/vnd.ez.api.Session+xml">
<title>EzCoreExtraBundle in practice (Intermediate)</title>
<description>EzCoreExtraBundle provides several additional features to eZ Publish/eZ Platform, such as themes and other devx candies. In this workshop, you will experience different functionnalities that may help you in your projects.</description>
<category>ez</category>
<protagonist>Jérôme Vieilledent (Code Rhapsodie)</protagonist>
<startDate>1504093500</startDate>
<endDate>1504105200</endDate>
<_contentId>579</_contentId>
<_locationId>576</_locationId>
</Session>
Now we should have our media type
Extend the Rest API : JSON with our endpoint?
curl ezrestapi.websc/api/ezp/v2/search/category/ez
-H "Accept: application/vnd.ez.api.SessionList+json”
Now we should have our media type
{
"SessionList": {
"_media-type": "application\/vnd.ez.api.SessionList+json",
"Sessions": [
{
"_media-type": "application\/vnd.ez.api.Session+json",
"title": "Real-life use cases leveraging Solr power",
"description": "",
"category": "ez",
"protagonist": "Petar \u0160panja (Netgen)",
"startDate": "1504078200",
"endDate": "1504089900",
"_contentId": 578,
"_locationId": 575
},
{
"_media-type": "application\/vnd.ez.api.Session+json",
"title": "EzCoreExtraBundle in practice (Intermediate)",
"description": "EzCoreExtraBundle provides several additional features to eZ Publish\/eZ Platform, such as themes and other devx candies. In this workshop, you will experience different functionnalities that may help you in your projects.",
"category": "ez",
"protagonist": "J\u00e9r\u00f4me Vieilledent (Code Rhapsodie)",
"startDate": "1504093500",
"endDate": "1504105200",
"_contentId": 579,
"_locationId": 576
},
eZPlatform’s Rest API
VueJs
What is VueJs
Created by Evan You in 2014
65000 stars on Github
Approachable, Versatile and Performant
Doc is life !
ALL YOU NEED IS DOC !
https://vuejs.org/v2/guide/
Component oriented framework
Component oriented framework
Parent knows datas
Children receive only required datas
Children send events to parent
Parent modify datas
A Vue
<div id="app">
<h1>Hello world !</h1>
</div>
var vue = new Vue({
name: 'app',
el: '#app'
})
Sample page
The page in component
<div>
<app-header></app-header>
<div class="left-col">
<news></news>
<slider></slider>
<news v-for="news in news-list"></news>
</div>
<div class="right-col">
<visits-list></visits-list>
<facebook-widget></facebook-widget>
</div>
<app-footer></app-footer>
</div>
Datas and computed
<div id="app">
<h1>Hello {{ firstName }} {{ lastName }}</h1>
</div>
var vue = new Vue({
name: 'app',
el: '#app',
data: {
firstName: 'Melvin',
lastName: 'Schultz'
},
computed: {
fullName () {
return this.firstName + ' ' + this.lastName
}
})
Conditions
<div id="app">
<h1 v-if="firstVisit">Welcome</h1>
<h1 v-else>Welcome back !</h1>
<h2> Hello {{ fullName }}</h2>
</div>
// ...
data: {
firstName: 'Melvin',
lastName: 'Schultz',
firstVisit: true
},
// ...
Loops
<!-- ... -->
<div v-if="!firstVisit">
<p>Last visits :</p>
<ul>
<li v-for="(date, key) in visits"> {{ date }}</li>
</ul>
</div>
<!-- ... -->
// ...
data: {
firstName: 'Melvin',
lastName: 'Schultz',
firstVisit: false,
visits: ['25/02/2017', '12/03/2017', '04/06/2017']
},
// ...
Directives
v-if, v-else, v-for, v-show, v-on, v-model, v-bind, v-XXXX
<div id="app" v-backgroundImage="’https://vuejs.org/images/logo.png’">
directives: {
backgroundImage: {
update (DOMElement, binding) {
DOMElement.style.background-image = `url("${decodeURI(binding.value)}")`
}
}
},
HTML attributes
<a v-bind:href="docLink">
All you need is doc
</a>
data: {
firstName: 'Melvin',
lastName: 'Schultz',
firstVisit: false,
docLink: 'https://vuejs.org/v2/guide'
},
Forms
<!-- ... -->
<form>
<label>First Name</label>
<input type="text" v-model="firstName">
<label>Last Name</label>
<input type="text" v-model="lastName">
</form>
<!-- ... -->
Events
<!-- ... -->
<form v-on:submit="console.log('submit handled')">
<label>First Name</label>
<input type="text" v-bind="firstName">
<label>Last Name</label>
<input type="text" v-bind="lastName">
</form>
<!-- ... -->
v-on:submit
v-on:click
v-on:focus
v-on:keyup
etc...
Methods
<!-- ... -->
<form v-on:submit="handleFormSubmission">
<label>First Name</label>
<input type="text" v-model="firstName">
<label>Last Name</label>
<input type="text" v-model="lastName">
</form>
<!-- ... -->
// ...
methods: {
handleFormSubmission () {
// do some api call
// with available data
}
}
// ...
The main force of VueJs : his data binding process
Some useful modules
Let’s try it !
Duplication of the EZWSC website
Steps
Demo :
Do it :
If we have time :
Install the VueJs dev environment
git clone https://bitbucket.org/summercamp17/ezwsc-vuejs.git restapi-vuejs
git checkout step-1-display-sessions
npm install
npm run dev
Go to localhost:8080
Display full session