Contenu connexe Similaire à REST in practice with Symfony2 (20) Plus de Daniel Londero (9) REST in practice with Symfony215. //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;!
19. 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"!
}!
21. //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;!
}
28. 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"!
}!
32. 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"!
}!
39. 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;!
41. //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;!
}!
44. //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')!
);!
}!
47. //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();!
}!
}!
50. //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!
);!
}
62. //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!
{!
...!
}
66. 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!
}
67. “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