SlideShare ist ein Scribd-Unternehmen logo
1 von 45
Downloaden Sie, um offline zu lesen
www.innoteam.it
PHPCR & API Platform
What it really means to build a CMF
Case Study - CMS
Premessa:
Un nostro cliente prestigioso ci ha chiesto di sviluppare un CMS custom

Requisiti:
• Restful

• Versionamento dei contenuti

• Multilingua

• Multisite
2
• Content Management Framework

• Un framework che offre gli strumenti
per la gestione dei contenuti

• E’ un toolbox per creare CMS custom

• Esempi:

• eZ Publish / eZ Platform

• Symfony CMF
• Content Management System

• E’ un sistema “pronto all’uso” per la
gestione dei contenuti

• Fornisce un interfaccia admin ben
precisa

• Esempi:

• Wordpress

• Craft CMS
3
CMS CMF
Symfony CMF
• E’ un insieme di bundle che possono essere usati per aggiungere
funzionalità CMS ad applicativi Symfony

• content-bundle, routing-bundle, menu-bundle, …

• Nato per applicativi Symfony server side(twig)

• SonataDoctrinePHPCRAdminBundle
4
La nostra proposta
5
6
• Creare un bundle riutilizzabile(vendor) che
sfrutta i bundle necessari di Symfony CMF

• Esporre in maniera RESTFul le operazioni con
API Platform

Back-end
• Applicativo Angular che consuma le API del
CMF per la gestione dei contenuti(CMS)
Back-office
• Applicativo Angular che consuma in GET le API
del CMF per la visualizzazione dei contenuti

Front-end
Symfony CMF e PHPCR
7
Architettura Symfony CMF
8
Content Repository | Apache Jackrabbit
PHPCR API | Jackalope
Doctrine PHPCR-ODM
DoctrinePHPCRBundle
Symfony CMF Bundles
Content Repository
• E’ uno storage engine che permette di accedere e manipolare contenuti anche di natura
eterogenea (e.g. pagine, video, immagini, recensioni, ecc..) in maniera uniforme.
• Esempio: Apache Jackrabbit
9
Albero dei Contenuti
1)Nodo
• rappresenta un contenuto

• raggiungibile da un path come in un
filesystem

2)Proprietà di un nodo
• contiene l’informazione

• semplice(stringa, bool, int)

• binaria(binary stream)
/
a b c
d e
path: /a/d

p1: true
path: /a/e

p1: “Titolo Pagina”

p2:
path: /a path: /b

p1: 25
path: /c

p1: 3.5
Workspace
• Un content repository è formato da n workspace.

• Ogni workspace ha il suo albero di contenuti.

• Sessione: E’ una connessione autenticata ad un singolo workspace
10
Content Repository
Workspace a Workspace b Workspace c
/
a b c
/ /
PHPCR(PHP Content Repository) API
• E’ una specifica di API Standard per interfacciarsi con qualsiasi
Content Repository in una maniera uniforme.

• E’ un porting di JCR(Java Content Repository) API
11
Jackalope
• E’ un’implementazione open-source di PHPCR API

• Supporta diversi driver backend (transport)
12
Jackalope Jackrabbit Jackalope DBAL
Content Repository
(Apache Jackrabbit)
RDBMS
(MySQL, SQLite,
Postgres)
Doctrine PHPCR-ODM
• E’ un ODM (Object Document Mapper)

• Utilizza il “Data Mapper” pattern per mappare Nodi PHPCR ad oggetti
PHP (Document)

• Supporta concetti PHPCR come children, references, versioning
13
DoctrinePHPCRBundle
• Interagisce con PHPCR API & Doctrine PHPCR-ODM per fornire il
Document Manager come servizio Symfony
14
Il nostro setup
15
Workspace default & live
16
Content Repository
Workspace Default Workspace Live
/
a
/
persist /a

v1 - DRAFT
Workspace default & live
17
Content Repository
Workspace Default Workspace Live
/
a
/
persist /a

v1 - DRAFT
v2 - DRAFT
v3 - DRAFT
v4 - DRAFT
v5 - DRAFT
Workspace default & live
18
Content Repository
Workspace Default Workspace Live
/
a
/
a
publish /a v5
v1 - DRAFT
v2 - DRAFT
v3 - DRAFT
v4 - DRAFT
v5 - PUBLISHED
v1 - PUBLISHED
CMFBundle
19
Content Repository | Apache Jackrabbit
PHPCR API | Jackalope
Doctrine PHPCR-ODM
DoctrinePHPCRBundle
Symfony CMF Bundles
API Platform
"require": {
"api-platform/core": "2.0.*",
"symfony-cmf/core-bundle": "2.0.*",
"symfony-cmf/menu-bundle": "2.1.*",
"symfony-cmf/routing-bundle": "2.0.*",
"symfony-cmf/content-bundle": "2.0.*",
"symfony-cmf/routing-auto-bundle": "2.0.*",
"symfony-cmf/routing-auto": "2.0.*",
"doctrine/phpcr-bundle": "1.3.*",
"doctrine/phpcr-odm": "1.4.*",
"jackalope/jackalope-jackrabbit": "1.3.*",
"phpcr/phpcr-shell": "^1.0"
},
composer.json
Config Dipendenze CMFBundle
20
/Resources/config/bundles.yml
# Config DoctrinePHPCRBundle Sessions
doctrine_phpcr:
session:
default_session: default
sessions:
default:
backend:
type: jackrabbit
connection: php_cr
url: "%jackrabbit_url%"
workspace: default
username: "%phpcr_user%"
password: "%phpcr_pass%"
live:
backend:
type: jackrabbit
connection: php_cr
url: "%jackrabbit_url%"
workspace: live
username: "%phpcr_user%"
password: "%phpcr_pass%"
# Config DoctrinePHPCRBundle Locales & DMs
doctrine_phpcr:
odm:
# locales:
# en: [it]
# it: [en]
# default_locale: it
locale_fallback: hardcoded
document_managers:
default:
session: default
mappings:
InnoteamCMFBundle: ~
live:
session: live
mappings:
InnoteamCMFBundle: ~
Config CMFBundle
21
app/config/config.yml
innoteam_cmf:
domains:
site1:
host: http://www.site1.com
default_locale: it
locales:
it: [en]
en: [it]
Iniettare Config
22
/DependencyInjection/InnoteamCMFExtension.php
class InnoteamCMFExtension extends Extension implements PrependExtensionInterface
{
public function prepend(ContainerBuilder $container)
{
$config = $this->processConfiguration(new Configuration(), $container->getExtensionConfig($this->getAlias()));
$extConfigs = Yaml::parse(file_get_contents(__DIR__ . '/../Resources/config/bundles.yml'));
foreach ($extConfigs as $key => $extConfig) {
switch ($key) {
case 'cmf_core':
$extConfig['multilang']['locales'] = array_keys($config['locales']);
break;
case 'doctrine_phpcr':
$extConfig['odm']['locales'] = $config['locales'];
$extConfig['odm']['default_locale'] = $config['default_locale'];
break;
}
$container->prependExtensionConfig($key, $extConfig);
}
}
}
Organizzazione Albero Contenuti
23
24
namespace InnoteamBundleCMFBundleDocument;
use SymfonyCmfBundleContentBundleDoctrinePhpcrStaticContent;
use DoctrineODMPHPCRMappingAnnotations as PHPCR;
/**
* @PHPCRDocument(
* translator="attribute",
* versionable="full",
* referenceable=true,
* repositoryClass=“InnoteamBundleCMFBundleRepositoryDocumentPageRepository"
* )
*/
class Page extends StaticContent implements WritableDocumentInterface
{
/** @PHPCRField(type="string", nullable=false) */
protected $name;
/** @PHPCRField(type="string", nullable=false, translated=true) */
protected $nameTranslated;
/** @PHPCRField(type="string", nullable=false) */
protected $type;
/** @PHPCRField(type="string", translated=true) */
protected $blocks;
/** @PHPCRField(type="string", translated=true) */
protected $status;
/** @PHPCRReferrers(referringDocument="AutoRoute", referencedBy="content") */
protected $routes;
DocumentWriter
25
namespace InnoteamBundleCMFBundleDocumentWriter;
class ChainDocumentWriter implements DocumentWriterInterface
{
/** @var DocumentWriterInterface[] */
protected $documentWriters;
public function __construct(array $documentWriters)
{
$this->documentWriters = $documentWriters;
}
public function publishDocument(WritableDocumentInterface $document, string $domainId, string $locale) : WritableDocumentInterface
{
foreach ($this->documentWriters as $documentWriter) {
try {
return $documentWriter->publishDocument($document, $domainId, $locale);
} catch (DocumentPublishingNotSupportedException $e) {
continue;
}
}
throw new DocumentPublishingNotSupportedException(sprintf(
"No Document Publisher found which supports publishing Document with id '%s' and class '%s'",
$document->getId(),
get_class($document)
));
}
public function persistDocument(WritableDocumentInterface $document, string $domainId, string $locale): WritableDocumentInterface {}
public function deleteDocument(WritableDocumentInterface $document, string $domainId, string $locale) {}
public function hideDocument(WritableDocumentInterface $document, string $domainId, string $locale): WritableDocumentInterface {}
public function unhideDocument(WritableDocumentInterface $document, string $domainId, string $locale): WritableDocumentInterface {}
}
Persist DocumentWriter
26
namespace InnoteamBundleCMFBundleDocumentWriterDocumentPersistWriter;
class PageDocumentPersistWriter extends BaseDocumentActionWriter implements DocumentPersistWriterInterface
{
public function persistDocument(
WritableDocumentInterface $document,
string $domainId,
string $locale
) : WritableDocumentInterface
{
$document->setStatus(StatusType::DRAFT);
$this->defaultManager->persist($document);
$this->defaultManager->bindTranslation($document, $locale);
$this->defaultManager->flush();
$metadata = $this->defaultManager->getClassMetadata(get_class($document));
if (false !== $metadata->versionable)
$this->defaultManager->checkpoint($document);
return $document;
}
}
API Platform
27
Item & Collection Operations
28
/Resources/config/api_resources/resources.yml
Item Operation
• operazione associata ad un singolo item

• getPage (GET /site1/it/pages/<uuid>)

• editPage (PUT /site1/it/pages/<uuid>)

Collection Operation
• Operazione GET che ritorna un listato di item

• es: GET /site1/pages-no-locale

• Operazione di creazione di un item:

• es: POST /site1/it/pages
resources:
InnoteamBundleCMFBundleDocumentPage:
shortname: 'Page'
itemOperations:
getPage:
route_name: 'api_cms_get_page'
normalization_context:
groups: [ 'page-details' ]
...
collectionOperations:
createPage:
route_name: 'api_cms_create_page'
normalization_context:
groups: [ 'page-details' ]
denormalization_context:
groups: [ 'page-create' ]
...
Operazioni READ
29
30
Richiesta GET Data Provider Normalization
Richiesta GET Page Data Provider Normalization
• Applicativo Client
richiede una pagina
• Ottiene l’oggetto
Document dal Content
Repository

• Serializza l’oggetto
Document in JSON

Workflow Operazioni READ
Item Operation Read - getPage
31
32
GET /site1/it/pages/<uuid>
Richiesta GET Data Provider Normalization
namespace InnoteamBundleCMFBundleController;
class PageController extends Controller
{
/**
* @Route(
* path="{domain}/{locale}/pages/{id}",
* methods={"GET"},
* requirements={"id"=".+", "domain"="w+", "locale"="^[a-z]{2}$"},
* name="api_cms_get_page",
* defaults={
* "_api_resource_class"=Page::class,
* "_api_item_operation_name"="getPage",
* "_api_item_operation_field"="id",
* "_api_respond"=true
* }
* )
*
* @param Page $data
* @return Page
*/
public function detailAction(Page $data)
{
return $data;
}
}
InnoteamBundleCMFBundleDocumentPage:
shortname: 'Page'
itemOperations:
getPage:
route_name: 'api_cms_get_page'
normalization_context:
groups: [ 'page-details' ]
33
ReadListener.php
Richiesta GET Data Provider Normalization
/**
* Calls the data provider and sets the data attribute.
*
* @param GetResponseEvent $event
* @throws NotFoundHttpException
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
try {
$attributes = RequestAttributesExtractor::extractAttributes($request);
} catch (RuntimeException $e) {
return;
}
if (isset($attributes['collection_operation_name'])) {
$data = $this->getCollectionData($request, $attributes);
} else {
$data = $this->getItemData($request, $attributes);
}
$request->attributes->set('data', $data);
}
34
Richiesta GET Data Provider Normalization
PageDocumentItemDataProvider.php
protected function doGetItem(
string $resourceClass,
string $id,
string $path,
string $operationName = null,
array $context = []
)
{
try {
/* @var Page $page */
$page = $this->manager->findTranslation(Page::class, $path, $this->locale, false);
} catch (MissingTranslationException $e) {
throw new NotFoundHttpException(sprintf(
"Page Document with path '%s' and locale '%s' not found", $path, $this->locale
));
}
return $page;
35
Richiesta GET Data Provider Normalization
namespace InnoteamBundleCMFBundleController;
class PageController extends Controller
{
/**
* @Route(
* path="{domain}/{locale}/pages/{id}",
* methods={"GET"},
* requirements={"id"=".+", "domain"="w+", "locale"="^[a-z]{2}$"},
* name="api_cms_get_page",
* defaults={
* "_api_resource_class"=Page::class,
* "_api_item_operation_name"="getPage",
* "_api_item_operation_field"="id",
* "_api_respond"=true
* }
* )
*
* @param Page $data
* @return Page
*/
public function detailAction(Page $data)
{
return $data;
}
36
Richiesta GET Data Provider Normalization
InnoteamBundleCMFBundleDocumentPage:
shortname: 'Page'
itemOperations:
getPage:
route_name: 'api_cms_get_page'
normalization_context:
groups: [ 'page-details' ]
InnoteamBundleCMFBundleDocumentPage:
attributes:
name:
groups: ['page-details', ...]
nameTranslated:
groups: ['page-details', ...]
type:
groups: ['page-details', ...]
blocks:
groups: ['page-details', ...]
{
"name": "my-article",
"nameTranslated": "mio-articolo",
"type": "generic-page",
"blocks": [
{
"type": "pb-block-title",
"attributes": {
"title": "Il mio primo articolo"
},
"enabled": true,
"name": "Title"
},
{
"type": "pb-block-intro",
"attributes": {
"title": "Lorem ipsum dolor sit amet",
"subtitle": "Sed ut perspiciatis unde omnis",
},
"enabled": true,
"name": "Introduction"
}
]
OPERAZIONI WRITE
37
38
01
S T E P
02
S T E P
03
S T E P
04
S T E P
Workflow Operazioni WRITE
1)Richiesta POST/PUT/DELETE
• Applicativo Client effettua
un‘operazione WRITE su un
Document/API Resource
2)Denormalization
• API Platform deserializza JSON in
oggetto Document/API Resource
3)WriteListener & Document Writer
• WriteListener mappa la richiesta al
metodo del Document Writer
4)Normalization
• Serializza l’oggetto Document in
JSON
Collection Operation Write -
createPage
39
40
POST /site1/it/pages
{“name”: “my-article”, “nameTranslated”: “mio-articolo”, …}
/**
* @Route(
* path="{domain}/{locale}/pages",
* methods={"POST"},
* requirements={"domain"="w+", "locale"="^[a-z]{2}$"},
* name="api_cms_create_page",
* defaults={
* "_api_resource_class"=Page::class,
* "_api_collection_operation_name"="createPage",
* "_api_respond"=true
* }
* )
*
* @Security("is_granted('ROLE_CMS_USER')")
*
* @param $data
* @return mixed
*/
public function createAction($data)
{
return $data;
}
InnoteamBundleCMFBundleDocumentPage:
shortname: 'Page'
collectionOperations:
createPage:
route_name: 'api_cms_create_page'
normalization_context:
groups: [ 'page-details' ]
denormalization_context:
groups: [ 'page-create' ]
01 02 03 04
41
/vendor/api-platform/core/src/EventListener/DeserializeListener.php
/**
* Deserializes the data sent in the requested format.
*
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->isMethodSafe(false) || $request->isMethod(Request::METHOD_DELETE)) {
return;
}
...
$request->attributes->set(
'data',
$this->serializer->deserialize(
$request->getContent(), $attributes['resource_class'], $format, $context
)
);
}
01 02 03 04
42
InnoteamBundleCMFBundleDocumentPage:
shortname: 'Page'
collectionOperations:
createPage:
route_name: 'api_cms_create_page'
normalization_context:
groups: [ 'page-details' ]
denormalization_context:
groups: [ 'page-create' ]
InnoteamBundleCMFBundleDocumentPage:
attributes:
name:
groups: ['page-create', ...]
nameTranslated:
groups: ['page-create', ...]
type:
groups: ['page-create', ...]
blocks:
groups: [‘page-create', ...]
/**
* @Route(
* path="{domain}/{locale}/pages",
* methods={"POST"},
* requirements={"domain"="w+", "locale"="^[a-z]{2}$"},
* name="api_cms_create_page",
* defaults={
* "_api_resource_class"=Page::class,
* "_api_collection_operation_name"="createPage",
* "_api_respond"=true
* }
* )
*
* @Security("is_granted('ROLE_CMS_USER')")
*
* @param $data
* @return mixed
*/
public function createAction($data)
{
return $data;
}
01 02 03 04
43
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$this->request = $event->getRequest();
$reqMethod = $this->request->getMethod();
$resourceClass = $this->request->attributes->get('_api_resource_class');
$opName = $this->getOperationName();
$document = $event->getControllerResult();
if (Request::METHOD_POST === $reqMethod &&
$opName === DocumentOperationMapper::getCreateOperationName($resourceClass)
) {
$event->setControllerResult(
$this->documentWriter->persistDocument($document, $this->domain, $this->locale)
);
}
}
/Bridge/Doctrine/PHPCR/EventListener/WriteListener.php
01 02 03 04
44
InnoteamBundleCMFBundleDocumentPage:
shortname: 'Page'
collectionOperations:
createPage:
route_name: 'api_cms_create_page'
normalization_context:
groups: [ 'page-details' ]
denormalization_context:
groups: [ 'page-create' ]
InnoteamBundleCMFBundleDocumentPage:
attributes:
name:
groups: ['page-details', ...]
nameTranslated:
groups: ['page-details', ...]
type:
groups: ['page-details', ...]
blocks:
groups: ['page-details', ...]
{
"name": "my-article",
"nameTranslated": "mio-articolo",
"type": "generic-page",
"blocks": [
{
"type": "pb-block-title",
"attributes": {
"title": "Il mio primo articolo"
},
"enabled": true,
"name": "Title"
},
{
"type": "pb-block-intro",
"attributes": {
"title": "Lorem ipsum dolor sit amet",
"subtitle": "Sed ut perspiciatis unde omnis",
},
"enabled": true,
"name": "Introduction"
}
]
01 02 03 04
Grazie!
45

Weitere ähnliche Inhalte

Was ist angesagt?

服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript
Qiangning Hong
 
Cloud Foundry Open Tour China (english)
Cloud Foundry Open Tour China (english)Cloud Foundry Open Tour China (english)
Cloud Foundry Open Tour China (english)
marklucovsky
 
Cloud Foundry Open Tour China
Cloud Foundry Open Tour ChinaCloud Foundry Open Tour China
Cloud Foundry Open Tour China
marklucovsky
 
parenscript-tutorial
parenscript-tutorialparenscript-tutorial
parenscript-tutorial
tutorialsruby
 

Was ist angesagt? (20)

服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript服务框架: Thrift & PasteScript
服务框架: Thrift & PasteScript
 
Debugging on rails
Debugging on railsDebugging on rails
Debugging on rails
 
Dessi docker kubernetes paas cloud
Dessi docker kubernetes paas cloudDessi docker kubernetes paas cloud
Dessi docker kubernetes paas cloud
 
Zend_Cache: how to improve the performance of PHP applications
Zend_Cache: how to improve the performance of PHP applicationsZend_Cache: how to improve the performance of PHP applications
Zend_Cache: how to improve the performance of PHP applications
 
Cloud Foundry Open Tour China (english)
Cloud Foundry Open Tour China (english)Cloud Foundry Open Tour China (english)
Cloud Foundry Open Tour China (english)
 
How DSL works on Ruby
How DSL works on RubyHow DSL works on Ruby
How DSL works on Ruby
 
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
Fluentd loves MongoDB, at MongoDB SV User Group, July 17, 2012
 
Http and Servlet basics
Http and Servlet basicsHttp and Servlet basics
Http and Servlet basics
 
RubyGems 3 & 4
RubyGems 3 & 4RubyGems 3 & 4
RubyGems 3 & 4
 
Cloud Foundry Open Tour China
Cloud Foundry Open Tour ChinaCloud Foundry Open Tour China
Cloud Foundry Open Tour China
 
PHP language presentation
PHP language presentationPHP language presentation
PHP language presentation
 
Fluentd meetup in japan
Fluentd meetup in japanFluentd meetup in japan
Fluentd meetup in japan
 
How to Begin Developing Ruby Core
How to Begin Developing Ruby CoreHow to Begin Developing Ruby Core
How to Begin Developing Ruby Core
 
Fluentd v0.14 Plugin API Details
Fluentd v0.14 Plugin API DetailsFluentd v0.14 Plugin API Details
Fluentd v0.14 Plugin API Details
 
Deploying Perl apps on dotCloud
Deploying Perl apps on dotCloudDeploying Perl apps on dotCloud
Deploying Perl apps on dotCloud
 
TorqueBox - Ruby Hoedown 2011
TorqueBox - Ruby Hoedown 2011TorqueBox - Ruby Hoedown 2011
TorqueBox - Ruby Hoedown 2011
 
Running Ruby on Solaris (RubyKaigi 2015, 12/Dec/2015)
Running Ruby on Solaris (RubyKaigi 2015, 12/Dec/2015)Running Ruby on Solaris (RubyKaigi 2015, 12/Dec/2015)
Running Ruby on Solaris (RubyKaigi 2015, 12/Dec/2015)
 
The Parenscript Common Lisp to JavaScript compiler
The Parenscript Common Lisp to JavaScript compilerThe Parenscript Common Lisp to JavaScript compiler
The Parenscript Common Lisp to JavaScript compiler
 
parenscript-tutorial
parenscript-tutorialparenscript-tutorial
parenscript-tutorial
 
Practical ngx_mruby
Practical ngx_mrubyPractical ngx_mruby
Practical ngx_mruby
 

Ähnlich wie PHPCR e API Platform: cosa significa davvero sviluppare un CMF con Symfony

Ähnlich wie PHPCR e API Platform: cosa significa davvero sviluppare un CMF con Symfony (20)

Caching with Memcached and APC
Caching with Memcached and APCCaching with Memcached and APC
Caching with Memcached and APC
 
ITB2017 - Keynote
ITB2017 - KeynoteITB2017 - Keynote
ITB2017 - Keynote
 
PHP Dependency Management with Composer
PHP Dependency Management with ComposerPHP Dependency Management with Composer
PHP Dependency Management with Composer
 
Architectural patterns for high performance microservices in kubernetes
Architectural patterns for high performance microservices in kubernetesArchitectural patterns for high performance microservices in kubernetes
Architectural patterns for high performance microservices in kubernetes
 
25 Intro to Symfony #burningkeyboards
25 Intro to Symfony #burningkeyboards25 Intro to Symfony #burningkeyboards
25 Intro to Symfony #burningkeyboards
 
Configuration Management in the Cloud - Cloud Phoenix Meetup Feb 2014
Configuration Management in the Cloud - Cloud Phoenix Meetup Feb 2014Configuration Management in the Cloud - Cloud Phoenix Meetup Feb 2014
Configuration Management in the Cloud - Cloud Phoenix Meetup Feb 2014
 
Amazon EC2 Container Service: Manage Docker-Enabled Apps in EC2
Amazon EC2 Container Service: Manage Docker-Enabled Apps in EC2Amazon EC2 Container Service: Manage Docker-Enabled Apps in EC2
Amazon EC2 Container Service: Manage Docker-Enabled Apps in EC2
 
High quality ap is with api platform
High quality ap is with api platformHigh quality ap is with api platform
High quality ap is with api platform
 
containerD
containerDcontainerD
containerD
 
Tech connect aws
Tech connect  awsTech connect  aws
Tech connect aws
 
Unleash your Symfony projects with eZ Platform
Unleash your Symfony projects with eZ PlatformUnleash your Symfony projects with eZ Platform
Unleash your Symfony projects with eZ Platform
 
Champaign-Urbana Javascript Meetup Talk (Jan 2020)
Champaign-Urbana Javascript Meetup Talk (Jan 2020)Champaign-Urbana Javascript Meetup Talk (Jan 2020)
Champaign-Urbana Javascript Meetup Talk (Jan 2020)
 
Spring in the Cloud - using Spring with Cloud Foundry
Spring in the Cloud - using Spring with Cloud FoundrySpring in the Cloud - using Spring with Cloud Foundry
Spring in the Cloud - using Spring with Cloud Foundry
 
PhpTour Lyon 2014 - Transparent caching & context aware http cache
PhpTour Lyon 2014 - Transparent caching & context aware http cachePhpTour Lyon 2014 - Transparent caching & context aware http cache
PhpTour Lyon 2014 - Transparent caching & context aware http cache
 
Unleashing Creative Freedom with MODX (2015-07-21 @ PHP FRL)
Unleashing Creative Freedom with MODX (2015-07-21 @ PHP FRL)Unleashing Creative Freedom with MODX (2015-07-21 @ PHP FRL)
Unleashing Creative Freedom with MODX (2015-07-21 @ PHP FRL)
 
The State of containerd
The State of containerdThe State of containerd
The State of containerd
 
Kubernetes for the PHP developer
Kubernetes for the PHP developerKubernetes for the PHP developer
Kubernetes for the PHP developer
 
PHP from soup to nuts Course Deck
PHP from soup to nuts Course DeckPHP from soup to nuts Course Deck
PHP from soup to nuts Course Deck
 
Php intro
Php introPhp intro
Php intro
 
Php intro
Php introPhp intro
Php intro
 

Kürzlich hochgeladen

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 

Kürzlich hochgeladen (20)

Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 

PHPCR e API Platform: cosa significa davvero sviluppare un CMF con Symfony

  • 1. www.innoteam.it PHPCR & API Platform What it really means to build a CMF
  • 2. Case Study - CMS Premessa: Un nostro cliente prestigioso ci ha chiesto di sviluppare un CMS custom Requisiti: • Restful • Versionamento dei contenuti • Multilingua • Multisite 2
  • 3. • Content Management Framework • Un framework che offre gli strumenti per la gestione dei contenuti • E’ un toolbox per creare CMS custom • Esempi: • eZ Publish / eZ Platform • Symfony CMF • Content Management System • E’ un sistema “pronto all’uso” per la gestione dei contenuti • Fornisce un interfaccia admin ben precisa • Esempi: • Wordpress • Craft CMS 3 CMS CMF
  • 4. Symfony CMF • E’ un insieme di bundle che possono essere usati per aggiungere funzionalità CMS ad applicativi Symfony • content-bundle, routing-bundle, menu-bundle, … • Nato per applicativi Symfony server side(twig) • SonataDoctrinePHPCRAdminBundle 4
  • 6. 6 • Creare un bundle riutilizzabile(vendor) che sfrutta i bundle necessari di Symfony CMF • Esporre in maniera RESTFul le operazioni con API Platform Back-end • Applicativo Angular che consuma le API del CMF per la gestione dei contenuti(CMS) Back-office • Applicativo Angular che consuma in GET le API del CMF per la visualizzazione dei contenuti Front-end
  • 7. Symfony CMF e PHPCR 7
  • 8. Architettura Symfony CMF 8 Content Repository | Apache Jackrabbit PHPCR API | Jackalope Doctrine PHPCR-ODM DoctrinePHPCRBundle Symfony CMF Bundles
  • 9. Content Repository • E’ uno storage engine che permette di accedere e manipolare contenuti anche di natura eterogenea (e.g. pagine, video, immagini, recensioni, ecc..) in maniera uniforme. • Esempio: Apache Jackrabbit 9 Albero dei Contenuti 1)Nodo • rappresenta un contenuto • raggiungibile da un path come in un filesystem 2)Proprietà di un nodo • contiene l’informazione • semplice(stringa, bool, int) • binaria(binary stream) / a b c d e path: /a/d p1: true path: /a/e p1: “Titolo Pagina” p2: path: /a path: /b p1: 25 path: /c p1: 3.5
  • 10. Workspace • Un content repository è formato da n workspace. • Ogni workspace ha il suo albero di contenuti. • Sessione: E’ una connessione autenticata ad un singolo workspace 10 Content Repository Workspace a Workspace b Workspace c / a b c / /
  • 11. PHPCR(PHP Content Repository) API • E’ una specifica di API Standard per interfacciarsi con qualsiasi Content Repository in una maniera uniforme. • E’ un porting di JCR(Java Content Repository) API 11
  • 12. Jackalope • E’ un’implementazione open-source di PHPCR API • Supporta diversi driver backend (transport) 12 Jackalope Jackrabbit Jackalope DBAL Content Repository (Apache Jackrabbit) RDBMS (MySQL, SQLite, Postgres)
  • 13. Doctrine PHPCR-ODM • E’ un ODM (Object Document Mapper) • Utilizza il “Data Mapper” pattern per mappare Nodi PHPCR ad oggetti PHP (Document) • Supporta concetti PHPCR come children, references, versioning 13
  • 14. DoctrinePHPCRBundle • Interagisce con PHPCR API & Doctrine PHPCR-ODM per fornire il Document Manager come servizio Symfony 14
  • 16. Workspace default & live 16 Content Repository Workspace Default Workspace Live / a / persist /a
 v1 - DRAFT
  • 17. Workspace default & live 17 Content Repository Workspace Default Workspace Live / a / persist /a
 v1 - DRAFT v2 - DRAFT v3 - DRAFT v4 - DRAFT v5 - DRAFT
  • 18. Workspace default & live 18 Content Repository Workspace Default Workspace Live / a / a publish /a v5 v1 - DRAFT v2 - DRAFT v3 - DRAFT v4 - DRAFT v5 - PUBLISHED v1 - PUBLISHED
  • 19. CMFBundle 19 Content Repository | Apache Jackrabbit PHPCR API | Jackalope Doctrine PHPCR-ODM DoctrinePHPCRBundle Symfony CMF Bundles API Platform "require": { "api-platform/core": "2.0.*", "symfony-cmf/core-bundle": "2.0.*", "symfony-cmf/menu-bundle": "2.1.*", "symfony-cmf/routing-bundle": "2.0.*", "symfony-cmf/content-bundle": "2.0.*", "symfony-cmf/routing-auto-bundle": "2.0.*", "symfony-cmf/routing-auto": "2.0.*", "doctrine/phpcr-bundle": "1.3.*", "doctrine/phpcr-odm": "1.4.*", "jackalope/jackalope-jackrabbit": "1.3.*", "phpcr/phpcr-shell": "^1.0" }, composer.json
  • 20. Config Dipendenze CMFBundle 20 /Resources/config/bundles.yml # Config DoctrinePHPCRBundle Sessions doctrine_phpcr: session: default_session: default sessions: default: backend: type: jackrabbit connection: php_cr url: "%jackrabbit_url%" workspace: default username: "%phpcr_user%" password: "%phpcr_pass%" live: backend: type: jackrabbit connection: php_cr url: "%jackrabbit_url%" workspace: live username: "%phpcr_user%" password: "%phpcr_pass%" # Config DoctrinePHPCRBundle Locales & DMs doctrine_phpcr: odm: # locales: # en: [it] # it: [en] # default_locale: it locale_fallback: hardcoded document_managers: default: session: default mappings: InnoteamCMFBundle: ~ live: session: live mappings: InnoteamCMFBundle: ~
  • 22. Iniettare Config 22 /DependencyInjection/InnoteamCMFExtension.php class InnoteamCMFExtension extends Extension implements PrependExtensionInterface { public function prepend(ContainerBuilder $container) { $config = $this->processConfiguration(new Configuration(), $container->getExtensionConfig($this->getAlias())); $extConfigs = Yaml::parse(file_get_contents(__DIR__ . '/../Resources/config/bundles.yml')); foreach ($extConfigs as $key => $extConfig) { switch ($key) { case 'cmf_core': $extConfig['multilang']['locales'] = array_keys($config['locales']); break; case 'doctrine_phpcr': $extConfig['odm']['locales'] = $config['locales']; $extConfig['odm']['default_locale'] = $config['default_locale']; break; } $container->prependExtensionConfig($key, $extConfig); } } }
  • 24. 24 namespace InnoteamBundleCMFBundleDocument; use SymfonyCmfBundleContentBundleDoctrinePhpcrStaticContent; use DoctrineODMPHPCRMappingAnnotations as PHPCR; /** * @PHPCRDocument( * translator="attribute", * versionable="full", * referenceable=true, * repositoryClass=“InnoteamBundleCMFBundleRepositoryDocumentPageRepository" * ) */ class Page extends StaticContent implements WritableDocumentInterface { /** @PHPCRField(type="string", nullable=false) */ protected $name; /** @PHPCRField(type="string", nullable=false, translated=true) */ protected $nameTranslated; /** @PHPCRField(type="string", nullable=false) */ protected $type; /** @PHPCRField(type="string", translated=true) */ protected $blocks; /** @PHPCRField(type="string", translated=true) */ protected $status; /** @PHPCRReferrers(referringDocument="AutoRoute", referencedBy="content") */ protected $routes;
  • 25. DocumentWriter 25 namespace InnoteamBundleCMFBundleDocumentWriter; class ChainDocumentWriter implements DocumentWriterInterface { /** @var DocumentWriterInterface[] */ protected $documentWriters; public function __construct(array $documentWriters) { $this->documentWriters = $documentWriters; } public function publishDocument(WritableDocumentInterface $document, string $domainId, string $locale) : WritableDocumentInterface { foreach ($this->documentWriters as $documentWriter) { try { return $documentWriter->publishDocument($document, $domainId, $locale); } catch (DocumentPublishingNotSupportedException $e) { continue; } } throw new DocumentPublishingNotSupportedException(sprintf( "No Document Publisher found which supports publishing Document with id '%s' and class '%s'", $document->getId(), get_class($document) )); } public function persistDocument(WritableDocumentInterface $document, string $domainId, string $locale): WritableDocumentInterface {} public function deleteDocument(WritableDocumentInterface $document, string $domainId, string $locale) {} public function hideDocument(WritableDocumentInterface $document, string $domainId, string $locale): WritableDocumentInterface {} public function unhideDocument(WritableDocumentInterface $document, string $domainId, string $locale): WritableDocumentInterface {} }
  • 26. Persist DocumentWriter 26 namespace InnoteamBundleCMFBundleDocumentWriterDocumentPersistWriter; class PageDocumentPersistWriter extends BaseDocumentActionWriter implements DocumentPersistWriterInterface { public function persistDocument( WritableDocumentInterface $document, string $domainId, string $locale ) : WritableDocumentInterface { $document->setStatus(StatusType::DRAFT); $this->defaultManager->persist($document); $this->defaultManager->bindTranslation($document, $locale); $this->defaultManager->flush(); $metadata = $this->defaultManager->getClassMetadata(get_class($document)); if (false !== $metadata->versionable) $this->defaultManager->checkpoint($document); return $document; } }
  • 28. Item & Collection Operations 28 /Resources/config/api_resources/resources.yml Item Operation • operazione associata ad un singolo item • getPage (GET /site1/it/pages/<uuid>) • editPage (PUT /site1/it/pages/<uuid>) Collection Operation • Operazione GET che ritorna un listato di item • es: GET /site1/pages-no-locale • Operazione di creazione di un item: • es: POST /site1/it/pages resources: InnoteamBundleCMFBundleDocumentPage: shortname: 'Page' itemOperations: getPage: route_name: 'api_cms_get_page' normalization_context: groups: [ 'page-details' ] ... collectionOperations: createPage: route_name: 'api_cms_create_page' normalization_context: groups: [ 'page-details' ] denormalization_context: groups: [ 'page-create' ] ...
  • 30. 30 Richiesta GET Data Provider Normalization Richiesta GET Page Data Provider Normalization • Applicativo Client richiede una pagina • Ottiene l’oggetto Document dal Content Repository • Serializza l’oggetto Document in JSON Workflow Operazioni READ
  • 31. Item Operation Read - getPage 31
  • 32. 32 GET /site1/it/pages/<uuid> Richiesta GET Data Provider Normalization namespace InnoteamBundleCMFBundleController; class PageController extends Controller { /** * @Route( * path="{domain}/{locale}/pages/{id}", * methods={"GET"}, * requirements={"id"=".+", "domain"="w+", "locale"="^[a-z]{2}$"}, * name="api_cms_get_page", * defaults={ * "_api_resource_class"=Page::class, * "_api_item_operation_name"="getPage", * "_api_item_operation_field"="id", * "_api_respond"=true * } * ) * * @param Page $data * @return Page */ public function detailAction(Page $data) { return $data; } } InnoteamBundleCMFBundleDocumentPage: shortname: 'Page' itemOperations: getPage: route_name: 'api_cms_get_page' normalization_context: groups: [ 'page-details' ]
  • 33. 33 ReadListener.php Richiesta GET Data Provider Normalization /** * Calls the data provider and sets the data attribute. * * @param GetResponseEvent $event * @throws NotFoundHttpException */ public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); try { $attributes = RequestAttributesExtractor::extractAttributes($request); } catch (RuntimeException $e) { return; } if (isset($attributes['collection_operation_name'])) { $data = $this->getCollectionData($request, $attributes); } else { $data = $this->getItemData($request, $attributes); } $request->attributes->set('data', $data); }
  • 34. 34 Richiesta GET Data Provider Normalization PageDocumentItemDataProvider.php protected function doGetItem( string $resourceClass, string $id, string $path, string $operationName = null, array $context = [] ) { try { /* @var Page $page */ $page = $this->manager->findTranslation(Page::class, $path, $this->locale, false); } catch (MissingTranslationException $e) { throw new NotFoundHttpException(sprintf( "Page Document with path '%s' and locale '%s' not found", $path, $this->locale )); } return $page;
  • 35. 35 Richiesta GET Data Provider Normalization namespace InnoteamBundleCMFBundleController; class PageController extends Controller { /** * @Route( * path="{domain}/{locale}/pages/{id}", * methods={"GET"}, * requirements={"id"=".+", "domain"="w+", "locale"="^[a-z]{2}$"}, * name="api_cms_get_page", * defaults={ * "_api_resource_class"=Page::class, * "_api_item_operation_name"="getPage", * "_api_item_operation_field"="id", * "_api_respond"=true * } * ) * * @param Page $data * @return Page */ public function detailAction(Page $data) { return $data; }
  • 36. 36 Richiesta GET Data Provider Normalization InnoteamBundleCMFBundleDocumentPage: shortname: 'Page' itemOperations: getPage: route_name: 'api_cms_get_page' normalization_context: groups: [ 'page-details' ] InnoteamBundleCMFBundleDocumentPage: attributes: name: groups: ['page-details', ...] nameTranslated: groups: ['page-details', ...] type: groups: ['page-details', ...] blocks: groups: ['page-details', ...] { "name": "my-article", "nameTranslated": "mio-articolo", "type": "generic-page", "blocks": [ { "type": "pb-block-title", "attributes": { "title": "Il mio primo articolo" }, "enabled": true, "name": "Title" }, { "type": "pb-block-intro", "attributes": { "title": "Lorem ipsum dolor sit amet", "subtitle": "Sed ut perspiciatis unde omnis", }, "enabled": true, "name": "Introduction" } ]
  • 38. 38 01 S T E P 02 S T E P 03 S T E P 04 S T E P Workflow Operazioni WRITE 1)Richiesta POST/PUT/DELETE • Applicativo Client effettua un‘operazione WRITE su un Document/API Resource 2)Denormalization • API Platform deserializza JSON in oggetto Document/API Resource 3)WriteListener & Document Writer • WriteListener mappa la richiesta al metodo del Document Writer 4)Normalization • Serializza l’oggetto Document in JSON
  • 39. Collection Operation Write - createPage 39
  • 40. 40 POST /site1/it/pages {“name”: “my-article”, “nameTranslated”: “mio-articolo”, …} /** * @Route( * path="{domain}/{locale}/pages", * methods={"POST"}, * requirements={"domain"="w+", "locale"="^[a-z]{2}$"}, * name="api_cms_create_page", * defaults={ * "_api_resource_class"=Page::class, * "_api_collection_operation_name"="createPage", * "_api_respond"=true * } * ) * * @Security("is_granted('ROLE_CMS_USER')") * * @param $data * @return mixed */ public function createAction($data) { return $data; } InnoteamBundleCMFBundleDocumentPage: shortname: 'Page' collectionOperations: createPage: route_name: 'api_cms_create_page' normalization_context: groups: [ 'page-details' ] denormalization_context: groups: [ 'page-create' ] 01 02 03 04
  • 41. 41 /vendor/api-platform/core/src/EventListener/DeserializeListener.php /** * Deserializes the data sent in the requested format. * * @param GetResponseEvent $event */ public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); if ($request->isMethodSafe(false) || $request->isMethod(Request::METHOD_DELETE)) { return; } ... $request->attributes->set( 'data', $this->serializer->deserialize( $request->getContent(), $attributes['resource_class'], $format, $context ) ); } 01 02 03 04
  • 42. 42 InnoteamBundleCMFBundleDocumentPage: shortname: 'Page' collectionOperations: createPage: route_name: 'api_cms_create_page' normalization_context: groups: [ 'page-details' ] denormalization_context: groups: [ 'page-create' ] InnoteamBundleCMFBundleDocumentPage: attributes: name: groups: ['page-create', ...] nameTranslated: groups: ['page-create', ...] type: groups: ['page-create', ...] blocks: groups: [‘page-create', ...] /** * @Route( * path="{domain}/{locale}/pages", * methods={"POST"}, * requirements={"domain"="w+", "locale"="^[a-z]{2}$"}, * name="api_cms_create_page", * defaults={ * "_api_resource_class"=Page::class, * "_api_collection_operation_name"="createPage", * "_api_respond"=true * } * ) * * @Security("is_granted('ROLE_CMS_USER')") * * @param $data * @return mixed */ public function createAction($data) { return $data; } 01 02 03 04
  • 43. 43 public function onKernelView(GetResponseForControllerResultEvent $event) { $this->request = $event->getRequest(); $reqMethod = $this->request->getMethod(); $resourceClass = $this->request->attributes->get('_api_resource_class'); $opName = $this->getOperationName(); $document = $event->getControllerResult(); if (Request::METHOD_POST === $reqMethod && $opName === DocumentOperationMapper::getCreateOperationName($resourceClass) ) { $event->setControllerResult( $this->documentWriter->persistDocument($document, $this->domain, $this->locale) ); } } /Bridge/Doctrine/PHPCR/EventListener/WriteListener.php 01 02 03 04
  • 44. 44 InnoteamBundleCMFBundleDocumentPage: shortname: 'Page' collectionOperations: createPage: route_name: 'api_cms_create_page' normalization_context: groups: [ 'page-details' ] denormalization_context: groups: [ 'page-create' ] InnoteamBundleCMFBundleDocumentPage: attributes: name: groups: ['page-details', ...] nameTranslated: groups: ['page-details', ...] type: groups: ['page-details', ...] blocks: groups: ['page-details', ...] { "name": "my-article", "nameTranslated": "mio-articolo", "type": "generic-page", "blocks": [ { "type": "pb-block-title", "attributes": { "title": "Il mio primo articolo" }, "enabled": true, "name": "Title" }, { "type": "pb-block-intro", "attributes": { "title": "Lorem ipsum dolor sit amet", "subtitle": "Sed ut perspiciatis unde omnis", }, "enabled": true, "name": "Introduction" } ] 01 02 03 04