REST in practice
with Symfony2
@dlondero
REST in practice with Symfony2
REST in practice with Symfony2
REST in practice with Symfony2
OFTEN...
Richardson Maturity Model
NOT
TALKING
ABOUT...
Level 0

POX - RPC
Level 1

RESOURCES
Level 2

HTTP VERBS
Level 3

HYPERMEDIA
TALKING
ABOUT HOW
TO DO
WHAT WE NEED	

!

•symfony/framework-standard-edition	

!

•friendsofsymfony/rest-bundle	

!

•jms/serializer-bundle	

!

•nelmio/api-doc-bundle
//src/Acme/ApiBundle/Entity/Product.php;!

!

use SymfonyComponentValidatorConstraints as Assert;!
use DoctrineORMMapping as ORM;!

!

/**!
* @ORMEntity!
* @ORMTable(name="product")!
*/!
class Product!
{!
/**!
* @ORMColumn(type="integer")!
* @ORMId!
* @ORMGeneratedValue(strategy="AUTO")!
*/!
protected $id;!

!

!

!

/**!
* @ORMColumn(type="string", length=100)!
* @AssertNotBlank()!
*/!
protected $name;!
/**!
* @ORMColumn(type="decimal", scale=2)!
*/!
protected $price;!
/**!
* @ORMColumn(type="text")!
*/!
protected $description;!
CRUD
Create	

HTTP POST
Request
POST /products HTTP/1.1!
Host: acme.com!
Content-Type: application/json!
!

{!
"name": "Product #1",!
"price": 19.90,!
"description": "Awesome product"!
}!
Response
HTTP/1.1 201 Created!
Location: http://acme.com/products/1!
Content-Type: application/json!
!

{!
"product": {!
"id": 1,!
"name": "Product #1",!
"price": 19.9,!
"description": "Awesome product"!
}!
//src/Acme/ApiBundle/Resources/config/routing.yml!
!
acme_api_product_post:!
pattern: /products!
defaults: { _controller: AcmeApiBundle:ApiProduct:post,
_format: json }!
requirements:!
_method: POST
//src/Acme/ApiBundle/Controller/ApiProductController.php!

!

use FOSRestBundleViewView;!

!

public function postAction(Request $request)!
{!
$product = $this->deserialize(!
'AcmeApiBundleEntityProduct',!
$request!
);!

!
!
!

!
!

if ($product instanceof Product === false) {!
return View::create(array('errors' => $product), 400);!
}!
$em = $this->getEM();!
$em->persist($product);!
$em->flush();!
$url = $this->generateUrl(!
'acme_api_product_get_single',!
array('id' => $product->getId()),!
true!
);!
$response = new Response();!
$response->setStatusCode(201);!
$response->headers->set('Location', $url);!
return $response;!

}
Read	

HTTP GET
Request
GET /products/1 HTTP/1.1!
Host: acme.com
Response
HTTP/1.1 200 OK!
Content-Type: application/json!
!

{!
"product": {!
"id": 1,!
"name": "Product #1",!
"price": 19.9,!
"description": "Awesome product"!
}!
public function getSingleAction(Product $product)!
{!
return array('product' => $product);!
}
Update	

HTTP PUT
Request
PUT /products/1 HTTP/1.1!
Host: acme.com!
Content-Type: application/json!
!

{!
"name": "Product #1",!
"price": 29.90,!
"description": "Awesome product"!
}!
Response
HTTP/1.1 204 No Content!
!

HTTP/1.1 200 OK!
Content-Type: application/json!
!

{!
"product": {!
"id": 1,!
"name": "Product #1",!
"price": 29.90,!
"description": "Awesome product"!
}!
//src/Acme/ApiBundle/Controller/ApiProductController.php!

!
use FOSRestBundleControllerAnnotationsView as RestView;!

!
/**!
* @RestView(statusCode=204)!
*/!
public function putAction(Product $product, Request $request)!
{!
$newProduct = $this->deserialize(!
'AcmeApiBundleEntityProduct',!
$request!
);!

!
if ($newProduct instanceof Product === false) {!
return View::create(array('errors' => $newProduct), 400);!
}!

!
$product->merge($newProduct);!

!
$this->getEM()->flush();!
}
Partial Update	

HTTP PATCH
Request
PATCH /products/1 HTTP/1.1!
Host: acme.com!
Content-Type: application/json!
!

{!
"price": 39.90,!
}!
Response
HTTP/1.1 204 No Content!
!

HTTP/1.1 200 OK!
Content-Type: application/json!
!

{!
"product": {!
"id": 1,!
"name": "Product #1",!
"price": 39.90,!
"description": "Awesome product"!
}!
//src/Acme/ApiBundle/Controller/ApiProductController.php!

!
use FOSRestBundleControllerAnnotationsView as RestView;!

!
/**!
* @RestView(statusCode=204)!
*/!
public function patchAction(Product $product, Request $request)!
{!
$validator = $this->get('validator');!

!
$raw = json_decode($request->getContent(), true);!

!
$product->patch($raw);!

!
if (count($errors = $validator->validate($product))) {!
return $errors;!
}!

!
$this->getEM()->flush();!
}
Delete	

HTTP DELETE
Request
DELETE /products/1 HTTP/1.1!
Host: acme.com
Response

HTTP/1.1 204 No Content
//src/Acme/ApiBundle/Controller/ApiProductController.php!

!
use FOSRestBundleControllerAnnotationsView as RestView;!

!
/**!
* @RestView(statusCode=204)!
*/!
public function deleteAction(Product $product)!
{!
$em = $this->getEM();!
$em->remove($product);!
$em->flush();!
}
Serialization
use JMSSerializerAnnotation as Serializer;!

!

/**!
* @SerializerExclusionPolicy("all")!
*/!
class Product!
{!
/**!
* @SerializerExpose!
* @SerializerType("integer")!
*/!
protected $id;!

!

!

!

/**!
* @SerializerExpose!
* @SerializerType("string")!
*/!
protected $name;!
/**!
* @SerializerExpose!
* @SerializerType("double")!
*/!
protected $price;!
/**!
* @SerializerExpose!
* @SerializerType("string")!
*/!
protected $description;!
Deserialization
//src/Acme/ApiBundle/Controller/ApiController.php!

!
protected function deserialize($class, Request $request, $format = 'json')!
{!
$serializer = $this->get('serializer');!
$validator = $this->get('validator');!

!
try {!
$entity = $serializer->deserialize(!
$request->getContent(),!
$class,!
$format!
);!
} catch (RuntimeException $e) {!
throw new HttpException(400, $e->getMessage());!
}!

!
if (count($errors = $validator->validate($entity))) {!
return $errors;!
}!

!
return $entity;!
}!
Testing
REST in practice with Symfony2
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!

use LiipFunctionalTestBundleTestWebTestCase;!

!

class ApiProductControllerTest extends WebTestCase!
{!
public function testPost()!
{!
$this->loadFixtures(array());!

!

$product = array(!
'name' => 'Product #1',!
'price' => 19.90,!
'description' => 'Awesome product',!
);!

!

$client = static::createClient();!
$client->request(!
'POST', !
'/products', !
array(), array(), array(), !
json_encode($product)!
);!

!

$this->assertEquals(201, $client->getResponse()->getStatusCode());!
$this->assertTrue($client->getResponse()->headers->has('Location'));!
$this->assertContains(!
"/products/1", !
$client->getResponse()->headers->get('Location')!
);!
}!
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
public function testPostValidation()!
{!
$this->loadFixtures(array());!

!
$product = array(!
'name' => '',!
'price' => 19.90,!
'description' => 'Awesome product',!
);!

!
$client = static::createClient();!
$client->request(!
'POST', !
'/products', !
array(), array(), array(), !
json_encode($product)!
);!

!
$this->assertEquals(400, $client->getResponse()->getStatusCode());!
}!
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
public function testGetAction()!
{!
$this->loadFixtures(array(!
'AcmeApiBundleTestsFixturesProduct',!
));!

!
$client = static::createClient();!
$client->request('GET', '/products');!

!
$this->isSuccessful($client->getResponse());!
$response = json_decode($client->getResponse()->getContent());!

!
$this->assertTrue(isset($response->products));!
$this->assertCount(1, $response->products);!

!
$product = $response->products[0];!
$this->assertSame('Product #1', $product->name);!
$this->assertSame(19.90, $product->price);!
$this->assertSame('Awesome product!', $product->description);!
}
//src/Acme/ApiBundle/Tests/Fixtures/Product.php!

!
use AcmeApiBundleEntityProduct as ProductEntity;!

!
use DoctrineCommonPersistenceObjectManager;!
use DoctrineCommonDataFixturesFixtureInterface;!

!
class Product implements FixtureInterface!
{!
public function load(ObjectManager $em)!
{!
$product = new ProductEntity();!
$product->setName('Product #1');!
$product->setPrice(19.90);!
$product->setDescription('Awesome product!');!

!
$em->persist($product);!
$em->flush();!
}!
}!
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
public function testGetSingleAction()!
{!
$this->loadFixtures(array(!
'AcmeApiBundleTestsFixturesProduct',!
));!

!
$client = static::createClient();!
$client->request('GET', '/products/1');!

!
$this->isSuccessful($client->getResponse());!
$response = json_decode($client->getResponse()->getContent());!

!
$this->assertTrue(isset($response->product));!
$this->assertEquals(1, $response->product->id);!
$this->assertSame('Product #1', $response->product->name);!
$this->assertSame(19.90, $response->product->price);!
$this->assertSame(!
'Awesome product!', !
$response->product->description!
);!
}
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
public function testPutAction()!
{!
$this->loadFixtures(array(!
'AcmeApiBundleTestsFixturesProduct',!
));!

!
$product = array(!
'name' => 'New name',!
'price' => 39.90,!
'description' => 'Awesome new description'!
);!

!
$client = static::createClient();!
$client->request(!
'PUT', !
'/products/1', !
array(), array(), array(), !
json_encode($product)!
);!

!
$this->isSuccessful($client->getResponse());!
$this->assertEquals(204, $client->getResponse()->getStatusCode());!
}
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
/**!
* @depends testPutAction!
*/!
public function testPutActionWithVerification()!
{!
$client = static::createClient();!
$client->request('GET', '/products/1');!
$this->isSuccessful($client->getResponse());!
$response = json_decode($client->getResponse()->getContent());!

!
$this->assertTrue(isset($response->product));!
$this->assertEquals(1, $response->product->id);!
$this->assertSame('New name', $response->product->name);!
$this->assertSame(39.90, $response->product->price);!
$this->assertSame(!
'Awesome new description', !
$response->product->description!
);!
}
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
public function testPatchAction()!
{!
$this->loadFixtures(array(!
'AcmeApiBundleTestsFixturesProduct',!
));!

!
$patch = array(!
'price' => 29.90!
);!

!
$client = static::createClient();!
$client->request(!
'PATCH', !
'/products/1', !
array(), array(), array(), !
json_encode($patch)!
);!
!
$this->isSuccessful($client->getResponse());!
$this->assertEquals(204, $client->getResponse()->getStatusCode());!
}
//src/Acme/ApiBundle/Tests/Controller/ApiProductControllerTest.php!

!
public function testDeleteAction()!
{!
$this->loadFixtures(array(!
'AcmeApiBundleTestsFixturesProduct',!
));!

!
$client = static::createClient();!
$client->request('DELETE', '/products/1');!
$this->assertEquals(204, $client->getResponse()->getStatusCode());!
}
Documentation
REST in practice with Symfony2
//src/Acme/ApiBundle/Controller/ApiProductController.php!

!

use NelmioApiDocBundleAnnotationApiDoc;!

!

/**!
* Returns representation of a given product!
*!
* **Response Format**!
*!
*
{!
*
"product": {!
*
"id": 1,!
*
"name": "Product #1",!
*
"price": 19.9,!
*
"description": "Awesome product"!
*
}!
*
}!
*!
* @ApiDoc(!
*
section="Products",!
*
statusCodes={!
*
200="OK",!
*
404="Not Found"!
*
}!
* )!
*/!
public function getSingleAction(Product $product)!
{!
return array('product' => $product);!
}!
REST in practice with Symfony2
REST in practice with Symfony2
Hypermedia?
There’s a bundle
for that™
willdurand/hateoas-bundle
fsc/hateoas-bundle
//src/Acme/ApiBundle/Entity/Product.php;!

!
use JMSSerializerAnnotation as Serializer;!
use FSCHateoasBundleAnnotation as Rest;!
use DoctrineORMMapping as ORM;!

!
/**!
* @ORMEntity!
* @ORMTable(name="product")!
* @SerializerExclusionPolicy("all")!
* @RestRelation(!
*
"self", !
*
href = @RestRoute("acme_api_product_get_single", !
*
parameters = { "id" = ".id" })!
* )!
* @RestRelation(!
*
"products", !
*
href = @RestRoute("acme_api_product_get")!
* )!
*/!
class Product!
{!
...!
}
REST in practice with Symfony2
application/hal+json
REST in practice with Symfony2
GET /orders/523 HTTP/1.1!
Host: example.org!
Accept: application/hal+json!
!

HTTP/1.1 200 OK!
Content-Type: application/hal+json!
!

{!
"_links": {!
"self": { "href": "/orders/523" },!
"invoice": { "href": "/invoices/873" }!
},!
"currency": "USD",!
"total": 10.20!
}
“What needs to be done to make the REST
architectural style clear on the notion that
hypertext is a constraint? In other words, if the
engine of application state (and hence the API)
is not being driven by hypertext, then it cannot
be RESTful and cannot be a REST API. Period.
Is there some broken manual somewhere that
needs to be fixed?”
Roy Fielding
“Anyway, being pragmatic, sometimes a level
2 well done guarantees a good API…”
Daniel Londero
“But don’t call it RESTful. Period.”
Roy Fielding
“Ok.”
Daniel Londero
THANKS
@dlondero

More Related Content

PPTX
Service approach for development REST API in Symfony2
PPTX
Service approach for development Rest API in Symfony2
PDF
Bullet: The Functional PHP Micro-Framework
PPTX
REST APIs in Laravel 101
PDF
Bootstrat REST APIs with Laravel 5
PDF
AngularJS with Slim PHP Micro Framework
PDF
RESTful API development in Laravel 4 - Christopher Pecoraro
ODP
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani
Service approach for development REST API in Symfony2
Service approach for development Rest API in Symfony2
Bullet: The Functional PHP Micro-Framework
REST APIs in Laravel 101
Bootstrat REST APIs with Laravel 5
AngularJS with Slim PHP Micro Framework
RESTful API development in Laravel 4 - Christopher Pecoraro
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani

What's hot (20)

PPT
Workshop: Symfony2 Intruduction: (Controller, Routing, Model)
ODP
Javascript laravel's friend
PPT
Web service with Laravel
ODP
REST API Laravel
PPT
Building Single Page Application (SPA) with Symfony2 and AngularJS
PDF
PDF
Laravel 로 배우는 서버사이드 #5
PDF
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
KEY
More to RoC weibo
PDF
The new features of PHP 7
PDF
Codeigniter : Two Step View - Concept Implementation
KEY
Rails web api 开发
PPTX
Zend framework
PDF
Introduction à Ruby
KEY
Silex, the microframework
PDF
Symfony internals [english]
PDF
How to driver your webservices with ansible
PPTX
Using WordPress as your application stack
PDF
Symfony2, Backbone.js & socket.io - SfLive Paris 2k13 - Wisembly
PPT
Zend Framework 1.8 Features Webinar
Workshop: Symfony2 Intruduction: (Controller, Routing, Model)
Javascript laravel's friend
Web service with Laravel
REST API Laravel
Building Single Page Application (SPA) with Symfony2 and AngularJS
Laravel 로 배우는 서버사이드 #5
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
More to RoC weibo
The new features of PHP 7
Codeigniter : Two Step View - Concept Implementation
Rails web api 开发
Zend framework
Introduction à Ruby
Silex, the microframework
Symfony internals [english]
How to driver your webservices with ansible
Using WordPress as your application stack
Symfony2, Backbone.js & socket.io - SfLive Paris 2k13 - Wisembly
Zend Framework 1.8 Features Webinar
Ad

Similar to REST in practice with Symfony2 (20)

PDF
Rest in practice con Symfony2
PDF
Living With Legacy Code
PDF
Es.next
PDF
OSCON2014 : Quick Introduction to System Tools Programming with Go
PDF
Swift - the future of iOS app development
PDF
Moving to modules
PDF
PHP Programming and its Applications workshop
PDF
From CakePHP to Laravel
PDF
Symfony2 - WebExpo 2010
PDF
Symfony2 - WebExpo 2010
PDF
Getting Into FLOW3 (DPC12)
PDF
Symfony2 - OSIDays 2010
PDF
api-platform: the ultimate API platform
ODP
Symfony CMF - PHP Conference Brazil 2011
PDF
TYPO3 Flow 2.0 Workshop T3BOARD13
PDF
InspiringCon15: Bringing TYPO3 Legacy Applications into the Flow
PDF
IPCSE12: Hands on FLOW3
PDF
TYPO3 Extension development using new Extbase framework
PDF
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
PDF
IPCSE12: Getting into FLOW3
Rest in practice con Symfony2
Living With Legacy Code
Es.next
OSCON2014 : Quick Introduction to System Tools Programming with Go
Swift - the future of iOS app development
Moving to modules
PHP Programming and its Applications workshop
From CakePHP to Laravel
Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010
Getting Into FLOW3 (DPC12)
Symfony2 - OSIDays 2010
api-platform: the ultimate API platform
Symfony CMF - PHP Conference Brazil 2011
TYPO3 Flow 2.0 Workshop T3BOARD13
InspiringCon15: Bringing TYPO3 Legacy Applications into the Flow
IPCSE12: Hands on FLOW3
TYPO3 Extension development using new Extbase framework
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
IPCSE12: Getting into FLOW3
Ad

More from Daniel Londero (9)

PDF
Magento meets vagrant
PDF
Random Tips for Remote Working
PDF
Lavorare da casa: siamo pronti?
PDF
Symfony2, SQL e NoSQL. Assieme. Si può.
PDF
Lavorare da casa: siamo pronti?
ODP
Io cache, tu database
ODP
Unit testing 101
PDF
Symfony e grandi numeri: si può fare!
ODP
Enterprise Open Source: Il caso PHP
Magento meets vagrant
Random Tips for Remote Working
Lavorare da casa: siamo pronti?
Symfony2, SQL e NoSQL. Assieme. Si può.
Lavorare da casa: siamo pronti?
Io cache, tu database
Unit testing 101
Symfony e grandi numeri: si può fare!
Enterprise Open Source: Il caso PHP

Recently uploaded (20)

PDF
Convolutional neural network based encoder-decoder for efficient real-time ob...
PDF
Taming the Chaos: How to Turn Unstructured Data into Decisions
PDF
NewMind AI Weekly Chronicles – August ’25 Week III
PDF
Architecture types and enterprise applications.pdf
PDF
Getting started with AI Agents and Multi-Agent Systems
PDF
Zenith AI: Advanced Artificial Intelligence
PDF
sustainability-14-14877-v2.pddhzftheheeeee
PPT
What is a Computer? Input Devices /output devices
PPTX
Benefits of Physical activity for teenagers.pptx
PDF
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
PPTX
Custom Battery Pack Design Considerations for Performance and Safety
PDF
A review of recent deep learning applications in wood surface defect identifi...
PPTX
Microsoft Excel 365/2024 Beginner's training
PDF
A contest of sentiment analysis: k-nearest neighbor versus neural network
PDF
Improvisation in detection of pomegranate leaf disease using transfer learni...
PDF
Produktkatalog für HOBO Datenlogger, Wetterstationen, Sensoren, Software und ...
PDF
Developing a website for English-speaking practice to English as a foreign la...
PPT
Module 1.ppt Iot fundamentals and Architecture
PPTX
Modernising the Digital Integration Hub
PDF
sbt 2.0: go big (Scala Days 2025 edition)
Convolutional neural network based encoder-decoder for efficient real-time ob...
Taming the Chaos: How to Turn Unstructured Data into Decisions
NewMind AI Weekly Chronicles – August ’25 Week III
Architecture types and enterprise applications.pdf
Getting started with AI Agents and Multi-Agent Systems
Zenith AI: Advanced Artificial Intelligence
sustainability-14-14877-v2.pddhzftheheeeee
What is a Computer? Input Devices /output devices
Benefits of Physical activity for teenagers.pptx
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
Custom Battery Pack Design Considerations for Performance and Safety
A review of recent deep learning applications in wood surface defect identifi...
Microsoft Excel 365/2024 Beginner's training
A contest of sentiment analysis: k-nearest neighbor versus neural network
Improvisation in detection of pomegranate leaf disease using transfer learni...
Produktkatalog für HOBO Datenlogger, Wetterstationen, Sensoren, Software und ...
Developing a website for English-speaking practice to English as a foreign la...
Module 1.ppt Iot fundamentals and Architecture
Modernising the Digital Integration Hub
sbt 2.0: go big (Scala Days 2025 edition)

REST in practice with Symfony2