Service approach for development REST API in Symfony2
-
Upload
sumy-php-user-group -
Category
Software
-
view
302 -
download
0
Transcript of Service approach for development REST API in Symfony2
Service approach for
development Rest API in
Symfony2Aleksey Kryvtsov @ Web-developer at CPCS
Development Rest API in Symfony22
● Interface as contracts.● Thin Controller, Fat Service.● The Content Negotiation in the HTTP and REST.● Form as API interface.
Rules
3
● FOSRestBundle● JMSSerializerBundle● NelmioApiDocBundle
List of bundles
// app/AppKernel.php
$bundles = [
//...
new FOS\RestBundle\FOSRestBundle(),new JMS\SerializerBundle\JMSSerializerBundle(),new Nelmio\ApiDocBundle\
NelmioApiDocBundle(),4
/api/v1/{_lang}/pages/{id}.{_format}
/api/v1/en/pages/{id}.jsonWill return a json file
/api/v1/en/pages/{id}.xml Will return a xml file
/api/v1/en/pages/{id} and /api/v1/en/pages/{id}.html Will return the web page file
Format of routing
5
# /app/config/routing.ymlacme_api_blog:
type: restprefix: /api/v1/{_lang}resource:
"@AcmeBlogBundle/Resources/config/routes.yml"
Adding the routes
6
# /src/Acme/BlogBundle/Resources/config/routes.ymlacme_api_blog_page:
resource: "Acme\BlogBundle\Controller\PageController"
name_prefix: api_v1_ # naming collision
Create a route file into the bundle
# php app/console route:debug | grep api_v1_
7
# /src/Acme/BlogBundle/Controller/PageController.phpnamespace Acme\BlogBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController as Rest;
class PageController extends Rest {// another methods POST, PUT, DELETE and etc...
/* @annotations */
public function getPageAction ($id) {return $this->get('acme_blog.blog_post.handler')->get($id);
}}
Controller: PageController::getPageAction ()
8
# /src/Acme/BlogBundle/Controller/PageController.phpuse FOS\RestBundle\Controller\Annotations as FOSRest;
/** * Get single Page * @FOSRest\Get("/pages/{id}", requirements={"id" = "\d+"}, name="get_page") * @FOSRest\View(serializerGroups={"page"}) * * @ApiDoc(/* documentation */) * * @param int $id The page id * * @return array * @throws NotFoundHttpException when page not exist or not found */public function getPageAction ($id) {}
Controller: PageController::getPageAction ()
9
# /src/Acme/BlogBundle/Entity/Page.phpnamespace Acme\BlogBundle\Entity;
class Page implements PageInterface {// fields, getters and setters
}
Interface as contract# /src/Acme/BlogBundle/Model/PageInterface.phpnamespace Acme\BlogBundle\Model;
interface PageInterface {}
10
Handler configuration. Part I# /src/Acme/BlogBundle/Resources/config/handlers.yml
services:1 acme_blog.page.entity:
class: Acme\BlogBundle\Entity\Page
2 acme_blog.blog_post.handler: class: Acme\BlogBundle\Handler\PageHandler arguments:3 - @doctrine.orm.entity_manager4 - @acme_blog.page.entity
11
HandlerInterface: PageHandlerInterface::get()# /src/Acme/BlogBundle/Handler/PageHandlerInterface.phpnamespace Acme\BlogBundle\Handler; use Acme\BlogBundle\Model\PageInterface;
interface PageHandlerInterface {// another methods POST, PUT, DELETE and etc...
/** * Gets a page by id * * @api * @param integer $id * * @return PageInterface */public function get ($id);}
12
Handler: PageHandler::__construct()# /src/Acme/BlogBundle/Handler/PageHandler.phpnamespace Acme\BlogBundle\Handler;
use Doctrine\ORM\EntityManager;use Acme\BlogBundle\Model\PageInterface;
class PageHandler implements PageHandlerInterface {
// another methods
/** * @return void */public function __construct(EntityManager $entityManager,PageInterface $pageEntity) // continue...
13
Handler: PageHandler::__construct()# /src/Acme/BlogBundle/Handler/PageHandler.phpnamespace Acme\BlogBundle\Handler;
class PageHandler implements PageHandlerInterface {
// another methods
/* public function __construct (...) */ {1 $this->entityManager = $entityManager;2 $this->pageEntity = $pageEntity;
3 $this->repository = $entityManager->getRepository(get_class($pageEntity));}}
14
Handler: PageHandler (UML)
15
Handler: PageHandler::get()# /src/Acme/BlogBundle/Handler/PageHandler.phpnamespace Acme\BlogBundle\Handler;
class PageHandler implements PageHandlerInterface {
// another methods POST, PUT, DELETE and etc...
/** * {@inheritdoc} */public function get ($id) {
return $this->repository->find($id);}}
16
Controller: PageController::getAllPagesAction()
17
URL: ~/pages
18
Diagram: getAction, getAllAction
# /src/Acme/BlogBundle/Controller/PageController.phpuse Symfony\Component\HttpFoundation\Request;
class PageController extends Rest {// another methods
/* @annotations -> URL: ~/pages */public function postPageAction(Request $request) {
return $this->get('acme_blog.blog_post.handler')->post($request->request->all());
}}
Controller: PageController::postPageAction ()
19
HandlerInterface: PageHandlerInterface::post()# /src/Acme/BlogBundle/Handler/PageHandlerInterface.php use Acme\BlogBundle\Model\PageInterface;
interface PageHandlerInterface {
/** * Creates a new page * * @api
* @param array $parameters* @param array $options * * @return PageInterface */public function post(array $parameters = [], array $options = []);}
20
Handler: PageHandler::post()# /src/Acme/BlogBundle/Handler/PageHandler.php
/* {@inheritdoc} */public function post ( array $parameters = [], array $options = []) {
return $this->processForm($this->pageClass, $parameters,$options,
“POST”);
}21
Handler configuration. Part II# /src/Acme/BlogBundle/Resources/config/handlers.ymlservices:
// ...acme_blog.page.form_type:
class: Acme\BlogBundle\Form\Type\PageFormType
acme_blog.blog_post.handler: class: Acme\BlogBundle\Handler\PageHandler arguments:
- @doctrine.orm.entity_manager - @acme_blog.page.entity - @form.factory - @acme_blog.page.form_type
22
Handler: PageHandler::__construct()# /src/Acme/BlogBundle/Handler/PageHandler.phpuse Symfony\Component\Form\FormFactoryInterface;use use Symfony\Component\Form\FormTypeInterface;
class PageHandler implements PageHandlerInterface {
/** * @return void */public function __construct (
//...FormFactoryInterface $formFactory, FormTypeInterface $formType
) {// ...$this->formFactory = $formFactory;$this->formType = $formType;
} 23
HandlerInterface: PageHandlerInterface::processForm()# /src/Acme/BlogBundle/Handler/PageHandlerInterface.phpuse Acme\BlogBundle\Model\PageInterface;
interface PageHandlerInterface {/** * Processes the form * * @api
* @param PageInterface $page* @param array $parameters
* @param array $options* @param string $method * * @return PageInterface */private function processForm(PageInterface $page, array $parameters = [], array $options = [],
$method)} 24
Handler: PageHandler::processForm()# /src/Acme/BlogBundle/Handler/PageHandler.php
protected function processForm (/*...*/) {1 $form = $this->formFactory->create($this->formType, $page, $options);
2 $form->submit($parameters, 'PATCH' !== $method);
3 if ($form->isValid()) {4 $this->entityManager->persist($page);5 $this->entityManager->flush($page);6 return $page;
}7 // throw new InvalidFormException(/*...*/)}
25
Controller: PageController::[patch|put]PageAction()
26
URL: ~/pages/{id}
Handler: PageHandler::patch()# /src/Acme/BlogBundle/Handler/PageHandler.php
// another methods
public function patch(array $parameters = [], array $options = []) {1 $page = $this->get($parameters[‘id’]);
2 return $this->processForm($page,$parameters,$options,“PATCH”
);}
27
Handler: PageHandler::put()# /src/Acme/BlogBundle/Handler/PageHandler.php
// another methods
public function put(array $parameters = [], array $options = []) {1 $page = $this->get($parameters[‘id’]);
2 return $this->processForm($page,$parameters,$options,“PUT”
);}
28
29
Diagram: postAction, putAction, patchAction
# /src/Acme/BlogBundle/Controller/PageController.php
class PageController extends Rest {// another methods
/* @annotations -> URL: ~/pages/{id} */public function deletePageAction($id) {
return $this->get('acme_blog.blog_post.handler')->delete($id);
}}
Controller: PageController::deletePageAction()
30
HandlerInterface: PageHandlerInterface::delete()# /src/Acme/BlogBundle/Handler/PageHandlerInterface.php use Acme\BlogBundle\Model\PageInterface;
interface PageHandlerInterface {
/** * Returns the result of removal * * @api
* @param integer $id * * @return string */public function delete($id);}
31
Handler: PageHandler::delete()# /src/Acme/BlogBundle/Handler/PageHandler.phpnamespace Acme\BlogBundle\Handler;
class PageHandler implements PageHandlerInterface {/** * {@inheritdoc} */public function delete ($id) {
1 $page = $this->get($id);
2 $this->entityManager->remove($page);3 $this->entityManager->flush();
}}
32
Diagram
33
34
UML
1. # app/config/routing.ymlNelmioApiDocBundle: resource: "@NelmioApiDocBundle/Resources/config/routing.yml" prefix: /api/doc
Documentation: ApiDoc
35
2. # /src/Acme/BlogBundle/Controller/PageController.php/** * @ApiDoc( * description = "Creates a new page from the submitted data.", * input = "Acme\BlogBundle\Form\PageType", * output = "Acme\BlogBundle\Entity\Page", * statusCodes = { * 200 = "Returned when successful", * 400 = "Returned when the form has errors" * } * ) */
36
JMSSerializer
37
# /src/Acme/BlogBundle/Entity/Page.phpuse Doctrine\ORM\Mapping as ORM;use JMS\Serializer\Annotation as JMS;/** * @ORM\Entity * @JMS\ExclusionPolicy("all") */class Page { /** * @JMS\Expose * @JMS\Type("AnotherClass") * @JMS\Groups({"page", "details"}) * @ORM\ManyToOne(targetEntity="AnotherClass") * @ORM\JoinColumn(name="column", referencedColumnName="id") */ protected $field;
{ "id": 1, "field": AnotherClass, "another": "anotherType"}
JMSSerializer: Annotations
38
● @ExclusionPolicy
● @Exclude
● @Expose
● @SerializedName
● @Since
● @Until
● @Groups
● @MaxDepth
● @AccessType
● @Accessor
● @AccessorOrder
● @VirtualProperty
● @Inline
● @ReadOnly
● @PreSerialize
● @PostSerialize
● @PostDeserialize
● @HandlerCallback
● @Discriminator
● @Type
● @XmlRoot
● @XmlAttribute
● @XmlValue
● @XmlList
● @XmlMap
● @XmlKeyValuePairs
Referenceshttp://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-apiBest Practices for Designing a Pragmatic RESTful APIhttp://williamdurand.fr/2012/08/02/rest-apis-with-symfony2-the-right-wayREST APIs with Symfony2: The Right Wayhttps://www.youtube.com/watch?v=Kkby5fG89K0Lukas Kahwe Smith (Symfony Camp)http://welcometothebundle.com/symfony2-rest-api-the-best-2013-waySymfony2 REST API: the best way
https://github.com/liuggio/symfony2-rest-api-the-best-2013-way
The github repository39