Agile Web Development
Liip.ch
–
RESTING WITH
Learn how to use and extend the OroCRM REST API
AGENDA
• Introduction to REST theory
• How to discover OroCRM Rest APIs
• How to use OroCRM REST APIs
• How to add a new REST APIs to OroCRM
INTRODUCTION TO REST
Everyone who has ever talked
about REST, at some point has said
something idiotic about REST.
Except for maybe Roy Fielding.
For example, I once thought it
meant converting all GET
parameters to virtual directories.
Differentiating GET and POST
seemed like overzealous
academics.
I only began to understand why
REST really makes sense and what
REST it is really about when I
started looking into cache headers
and reverse proxies.
REST is all about leveraging HTTP
and constraining your application
to a set of rules, so that users of
your API can safely apply
assumptions about the behavior of
your application.
HTTP Request Anatomy
GET /notes HTTP/1.1
Host: symfony-rest-edition.lo
Accept: application/json;q=0.9,*/*;q=0.8
Content-Type: application/json
Content-Length: length
HTTP Response Anatomy
HTTP/1.1 200 OK
Allow: GET, POST
Cache-Control: max-age=15, public, s-maxage=30
Content-Type: application/json
Date: Wed, 15 Jan 2014 15:09:01 GMT
Last-Modified: Wed, 15 Jan 2014 14:09:03 GMT
Server: Apache/2.2.24 (Unix) DAV/2 PHP/5.4.20 mod_ssl/
2.2.24 OpenSSL/0.9.8y
Vary: Accept-Encoding,Accept-Language
{"notes":["a","b","c"]}
REST MATURITY MODEL
http://martinfowler.com/articles/richardsonMaturityModel.html
RMM LEVEL 0
• Aka "The Swamp of POX"
• HTTP as a tunneling mechanism
• "Procedural" communication (RPC)
• Single endpoint (per operation)
RMM LEVEL 0
http://martinfowler.com/articles/richardsonMaturityModel.html
RMM LEVEL 1
• Aka "Resources"
• Individual resources, i.e. URIs
• "Object orientated" communication
RMM LEVEL 1
http://martinfowler.com/articles/richardsonMaturityModel.html
RMM LEVEL 2
• Aka "HTTP Verbs"
• Client uses specific HTTP method
• Server uses HTTP status codes
RMM LEVEL 2
http://martinfowler.com/articles/richardsonMaturityModel.html
HTTP VERBS
Method Safe Idempotent
GET yes yes
HEAD yes yes
POST no no
PUT no yes
DELETE no yes (*)
.. no no
SAFE VS. IDEMPOTENT
• Safe means cacheable
• Idempotent means result independent of the # of executions, but..
• Does this apply to server state or also to the HTTP response?
• Is DELETE idempotent or not, ie. what should be the response
for a DELETE requests on a non existent resource? 404 or 200?
HTTP STATUS CODES
Code range Description Example
1xx Information 100 - Continue
2xx Successful 201 - Created
3xx Redirection 301 - Moved
Permanently
4xx Client Error 404 - Not Found
5xx Server Error 501 - Not
Implemented
RMM LEVEL 3
• Aka "Hypermedia Control"
• Service discovery via link relations
• ATOM, HAL, JSON-LD, IANA Link Rel
RMM LEVEL 3
http://martinfowler.com/articles/richardsonMaturityModel.html
HYPERTEXT AS THE ENGINE OF
APPLICATION STATE
=
HATEOAS
RMM VS REST VS REAL LIFE
• Most developers consider RMM Level 2 sufficient for REST
• RMM Level 3 is a precondition but not sufficient for REST
• ie. RMM only covers a subset of what REST requires
• RMM Level 3 makes URI forma ing ma er less
RMM VS REST VS REAL LIFE
• Browsers are bad REST clients
• REST is protocol independent
• Few (no?) clients really leverage HATEOAS
CONTENT TYPE NEGOTIATION
UNIFIED RESOURCE IDENTIFIER
• URIs identify resources
• URIs are format independent
• URI "file extensions" != RESTful
MEDIA TYPES
• Identifies a representation format
• Custom types use application/vnd.[XYZ]
• Used inside the Accept / Content-Type headers
Header Description
Content-Type HTTP message format
Accept HTTP response format preference
• Finding appropriate response format
• No standardized algorithm available
• Apache mod_negotiation algorithm is documented
• Also covers encoding (Accept-Encoding) and language (Accept-
Language) negotiation
CONTENT TYPE NEGOTIATION
EXAMPLE
Accept: application/json, application/xml;q=0.9, text/
html;q=0.8, text/*;q=0.7, */*;q=0.5
Priority Description
q=1.0 application/json
q=0.9 application/xml
q=0.8 text/html
q=0.7 text/* (ie. any text)
q=0.5 */* (ie. any media type)
OUT OF THE BOX OROCRM REST APIS
• $> app/console router:debug | grep api
• API Docs via NelmioApiDocBundle, ie. “/api/doc/“
• Enduser friendly API overview
• Includes a sandbox to try out API calls
• Information is extracted from Annotations on the Controllers
USING OROCRM REST APIS
• Constructing the URL for: api/rest/{version}/accounts/{id}.{_format}
• Symfony uses a syntax based on RFC 6570 for templated URI
• {version} = “v1” (recommended) or “latest”
• {id} = ID of an Account instance
• {_format} = lazy content type negotiation, ie. “.json”, or use HTTP header
“Accept: application/json”
http://httpie.org/
HTTPie (pronounced
aych-tee-tee-pie) is a
command line HTTP
client. Its goal is to
make CLI interaction
with web services as
human-friendly as
possible. It provides a
simple “http” command
that allows for sending
arbitrary HTTP requests
using a simple and
natural syntax, and
displays colorized
output. HTTPie can be
used for testing,
debugging, and
generally interacting
with HTTP servers.
WSSE SECURITY
$> http --json http://orocrm.lo/api/rest/v1/accounts
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Content-Type: application/json
Date: Wed, 22 Jul 2015 12:09:08 GMT
Server: Apache/2.4.10 (Unix) PHP/5.6.9
Set-Cookie: CRMID=i45n7g8phauhrfr2h53fh576q1; path=/;
HttpOnly
Transfer-Encoding: chunked
WWW-Authenticate: WSSE realm="Secured API",
profile="UsernameToken"
GENERATING THE WSSE HEADER
$> app/console oro:wsse:generate-header
c51ef6cb3e87dcc6f077b93f0ceb778d23669364
To use WSSE authentication add following headers to the
request:
Authorization: WSSE profile="UsernameToken"
X-WSSE: UsernameToken Username="admin",
PasswordDigest="pgkLCLmWcld9xkTUJ4Rxnj+Ww50=",
Nonce="MjU3ZWMzYzI1ZTVmYjg5NA==",
Created=“2015-07-22T14:12:27+02:00"
http://www.orocrm.com/documentation/index/current/cookbook/how-to-use-wsse-authentication
READING OROCRM REST APIS
$> http --json http://orocrm.lo/api/rest/v1/accounts
'X-WSSE: UsernameToken Username="admin",
PasswordDigest="pgkLCLmWcld9xkTUJ4Rxnj+Ww50=",
Nonce="MjU3ZWMzYzI1ZTVmYjg5NA==",
Created=“2015-07-22T14:12:27+02:00"'
[
{
"contacts": {},
"createdAt": "2015-07-06T09:12:31+00:00",
"defaultContact": "Mr. Jerry Coleman",
"id": 1,
"name": "Life Plan Counselling",
"organization": "Liip Test",
WRITING TO OROCRM REST APIS
$> http POST --json http://orocrm.lo/api/rest/v1/accounts/1
'X-WSSE: UsernameToken Username="admin",
PasswordDigest="pgkLCLmWcld9xkTUJ4Rxnj+Ww50=",
Nonce="MjU3ZWMzYzI1ZTVmYjg5NA==",
Created=“2015-07-22T14:12:27+02:00"' < account.json
$> cat account.json
{
"account": {
"owner": 2
}
}
DEMO TIME
APPROACHES TO CREATE A NEW API
• OroCRM has uses various different approaches
• Long term goal of OroCRM is to provide SOAP and REST via the same code
• Available approaches:
• Helper methods in Oro Platform RestGetController
• Serialization via JMS Serializer or via Symfony core Serializer
• Oro Platform EntitySerializer is a work in progress which make it even
easier to cover REST and SOAP with the same controller
BASIC CONTROLLER SETUP
<?php



namespace AcmeBundleCartBundleControllerApiRest;



use FOSRestBundleControllerAnnotationsNamePrefix;

use FOSRestBundleRoutingClassResourceInterface;

use OroBundleSoapBundleControllerApiRestRestController;



/**

* @NamePrefix("acme_api_")

*/

class CartController extends RestController implements ClassResourceInterface

{

..
}
IMPLEMENTING GETTING A LIST (1/3)
/**

* REST GET list

*

* @ApiDoc(

* description="Get all carts",

* resource=true

* )

* @AclAncestor("orocrm_magento_cart_view")

*

* @return JsonResponse

*/

public function cgetAction()

{

/** @var Cart[] $carts */

$carts = $this->getManager()->getListQueryBuilder()->getQuery()->execute();



return new JsonResponse(

$this->getPreparedItems($carts, self::$fields),

Codes::HTTP_OK

);

}
IMPLEMENTING GETTING A LIST (2/3)
<?php



namespace AcmeBundleCartBundleControllerApiRest;

use OroCRMBundleMagentoBundleEntityCart;



use SymfonyComponentHttpFoundationJsonResponse;

use FOSRestBundleControllerAnnotationsNamePrefix;

use FOSRestBundleRoutingClassResourceInterface;

use FOSRestBundleUtilCodes;

use NelmioApiDocBundleAnnotationApiDoc;



use OroBundleSecurityBundleAnnotationAclAncestor;

use OroBundleSoapBundleControllerApiRestRestController;
class CartController extends RestController implements ClassResourceInterface

{
static $fields = array('id', 'subTotal', 'grandTotal', 'taxAmount', 'customer')
..

}
IMPLEMENTING GETTING A LIST (3/3)
/**

* {@inheritdoc}

*/

public function getManager()

{

return $this->get('acme.cart.manager.api');

}



/**

* Prepare entity field for serialization

*

* @param string $field

* @param mixed $value

*/

protected function transformEntityField($field, &$value)

{

if ($value instanceof Customer) {

$value = array('name' => $value->getLastName());

return;

}

parent::transformEntityField($field, $value);

}
IMPLEMENTING GETTING A ONE ENTITY
/**

* REST GET one

*

* @ApiDoc(

* description="Get one cart",

* resource=true

* )

* @AclAncestor("orocrm_magento_cart_view")

* @param int $cartId

*

* @return JsonResponse

*/

public function getAction($cartId)

{

/** @var Cart $cart */

$cart = $this->getManager()->find($cartId);



return new JsonResponse(

$this->getPreparedItem($cart, self::$fields),

empty($cartId) ? Codes::HTTP_NOT_FOUND : Codes::HTTP_OK

);

}
FULL EXAMPLE
<?php



namespace OroCRMBundleMagentoBundleControllerApiRest;



use OroCRMBundleMagentoBundleEntityCustomer;

use OroCRMBundleMagentoBundleEntityCart;



use SymfonyComponentHttpFoundationJsonResponse;



use FOSRestBundleControllerAnnotationsNamePrefix;

use FOSRestBundleRoutingClassResourceInterface;

use FOSRestBundleUtilCodes;



use NelmioApiDocBundleAnnotationApiDoc;



use OroBundleSecurityBundleAnnotationAclAncestor;

use OroBundleSoapBundleControllerApiRestRestController;



/**

* @NamePrefix("acme_api_")

*/

class CartController extends RestController implements ClassResourceInterface

{

static $fields = array('id', 'subTotal', 'grandTotal', 'taxAmount', 'customer');



/**

* REST GET list

*

* @ApiDoc(

* description="Get all carts",

* resource=true

* )

* @AclAncestor("orocrm_magento_cart_view")

*

* @return JsonResponse

*/

public function cgetAction()

{

/** @var Cart[] $carts */

$carts = $this->getManager()->getListQueryBuilder()->getQuery()->execute();



return new JsonResponse(

$this->getPreparedItems($carts, self::$fields),

Codes::HTTP_OK

);

}

/**

* REST GET one

*

* @ApiDoc(

* description="Get one cart",

* resource=true

* )

* @AclAncestor("orocrm_magento_cart_view")

* @param int $cartId

*

* @return JsonResponse

*/

public function getAction($cartId)

{

/** @var Cart $cart */

$cart = $this->getManager()->find($cartId);



return new JsonResponse(

$this->getPreparedItem($cart, self::$fields),

empty($cartId) ? Codes::HTTP_NOT_FOUND : Codes::HTTP_OK

);

}



public function getManager()

{

return $this->get('orocrm_magento.cart.manager.api');

}



protected function transformEntityField($field, &$value)

{

if ($value instanceof Customer) {

$value = array('name' => $value->getLastName());

return;

}

parent::transformEntityField($field, $value);

}



public function getFormHandler()

{

throw new BadMethodCallException('FormHandler is not available.');

}

}

REQUIRED SERVICE
# services.yml
parameters:
acme.cart.manager.api.class: OroBundleSoapBundle..ApiEntityManager

services:
acme.cart.manager.api:

class: %acme.cart.manager.api.class%

parent: oro_soap.manager.entity_manager.abstract

arguments:

- %orocrm_magento.entity.cart.class%

- @doctrine.orm.entity_manager
REQUIRED ROUTE DEFINITION
# routing.yml
acme_bundle_cart_api:

resource: "@AcmeCartBundle/Controller/Api/Rest/CartController.php"

type: rest

prefix: api/rest/{version}

requirements:

version: latest|v1

defaults:

version: latest
USING OUR NEW OROCRM REST API
$> http --json http://orocrm.lo/api/rest/v1/carts ‘X-WSSE..’
..
[
{
"customer": {
"name": "Clark"
},
"grandTotal": "163.9248",
"id": 1,
"subTotal": "163.9248",
"taxAmount": "12.6748"
},
TESTING OROCRM REST APIS (1/2)
use OroBundleTestFrameworkBundleTestWebTestCase;



class ApiCartControllerTest extends WebTestCase

{

/* Taken from BazingaRestExtraBundle */

protected function assertJsonResponse($response, $statusCode = 200)

{

$this->assertEquals(

$statusCode, $response->getStatusCode(),

$response->getContent()

);

$this->assertTrue(

$response->headers->contains('Content-Type', 'application/json'),

$response->headers

);

}



protected function setUp()

{

$this->initClient(array(), $this->generateWsseAuthHeader());

}
..

TESTING OROCRM REST APIS (2/2)
public function testGetCarts()

{

$this->client->request('HEAD', '/api/rest/v1/carts');

$response = $this->client->getResponse();

$this->assertEquals(200, $response->getStatusCode(), $response->getContent());



$this->client->request('GET', ‘/api/rest/v1/carts');

$response = $this->client->getResponse();



$this->assertJsonResponse($response);

$this->assertEquals(‘..’, $response->getContent());

}



public function testGetCart()

{

$this->client->request('GET', '/api/rest/v1/carts/1');

$response = $this->client->getResponse();



$this->assertJsonResponse($response);

$this->assertEquals('{"id":
1,"subTotal":"163.9248","grandTotal":"163.9248","taxAmount":"12.6748","customer":
{"name":"Clark"}}', $response->getContent());

}
DEMO TIME
Agile Web Development
Liip.ch
–
QUESTIONS?
THANK YOU VERY MUCH!

Resting with OroCRM Webinar

  • 1.
    Agile Web Development Liip.ch – RESTINGWITH Learn how to use and extend the OroCRM REST API
  • 2.
    AGENDA • Introduction toREST theory • How to discover OroCRM Rest APIs • How to use OroCRM REST APIs • How to add a new REST APIs to OroCRM
  • 3.
  • 4.
    Everyone who hasever talked about REST, at some point has said something idiotic about REST. Except for maybe Roy Fielding.
  • 5.
    For example, Ionce thought it meant converting all GET parameters to virtual directories. Differentiating GET and POST seemed like overzealous academics.
  • 6.
    I only beganto understand why REST really makes sense and what REST it is really about when I started looking into cache headers and reverse proxies.
  • 7.
    REST is allabout leveraging HTTP and constraining your application to a set of rules, so that users of your API can safely apply assumptions about the behavior of your application.
  • 8.
    HTTP Request Anatomy GET/notes HTTP/1.1 Host: symfony-rest-edition.lo Accept: application/json;q=0.9,*/*;q=0.8 Content-Type: application/json Content-Length: length
  • 9.
    HTTP Response Anatomy HTTP/1.1200 OK Allow: GET, POST Cache-Control: max-age=15, public, s-maxage=30 Content-Type: application/json Date: Wed, 15 Jan 2014 15:09:01 GMT Last-Modified: Wed, 15 Jan 2014 14:09:03 GMT Server: Apache/2.2.24 (Unix) DAV/2 PHP/5.4.20 mod_ssl/ 2.2.24 OpenSSL/0.9.8y Vary: Accept-Encoding,Accept-Language {"notes":["a","b","c"]}
  • 10.
  • 11.
    RMM LEVEL 0 •Aka "The Swamp of POX" • HTTP as a tunneling mechanism • "Procedural" communication (RPC) • Single endpoint (per operation)
  • 12.
  • 13.
    RMM LEVEL 1 •Aka "Resources" • Individual resources, i.e. URIs • "Object orientated" communication
  • 14.
  • 15.
    RMM LEVEL 2 •Aka "HTTP Verbs" • Client uses specific HTTP method • Server uses HTTP status codes
  • 16.
  • 17.
    HTTP VERBS Method SafeIdempotent GET yes yes HEAD yes yes POST no no PUT no yes DELETE no yes (*) .. no no
  • 18.
    SAFE VS. IDEMPOTENT •Safe means cacheable • Idempotent means result independent of the # of executions, but.. • Does this apply to server state or also to the HTTP response? • Is DELETE idempotent or not, ie. what should be the response for a DELETE requests on a non existent resource? 404 or 200?
  • 19.
    HTTP STATUS CODES Coderange Description Example 1xx Information 100 - Continue 2xx Successful 201 - Created 3xx Redirection 301 - Moved Permanently 4xx Client Error 404 - Not Found 5xx Server Error 501 - Not Implemented
  • 20.
    RMM LEVEL 3 •Aka "Hypermedia Control" • Service discovery via link relations • ATOM, HAL, JSON-LD, IANA Link Rel
  • 21.
  • 22.
    HYPERTEXT AS THEENGINE OF APPLICATION STATE = HATEOAS
  • 23.
    RMM VS RESTVS REAL LIFE • Most developers consider RMM Level 2 sufficient for REST • RMM Level 3 is a precondition but not sufficient for REST • ie. RMM only covers a subset of what REST requires • RMM Level 3 makes URI forma ing ma er less
  • 24.
    RMM VS RESTVS REAL LIFE • Browsers are bad REST clients • REST is protocol independent • Few (no?) clients really leverage HATEOAS
  • 25.
  • 26.
    UNIFIED RESOURCE IDENTIFIER •URIs identify resources • URIs are format independent • URI "file extensions" != RESTful
  • 27.
    MEDIA TYPES • Identifiesa representation format • Custom types use application/vnd.[XYZ] • Used inside the Accept / Content-Type headers Header Description Content-Type HTTP message format Accept HTTP response format preference
  • 28.
    • Finding appropriateresponse format • No standardized algorithm available • Apache mod_negotiation algorithm is documented • Also covers encoding (Accept-Encoding) and language (Accept- Language) negotiation CONTENT TYPE NEGOTIATION
  • 29.
    EXAMPLE Accept: application/json, application/xml;q=0.9,text/ html;q=0.8, text/*;q=0.7, */*;q=0.5 Priority Description q=1.0 application/json q=0.9 application/xml q=0.8 text/html q=0.7 text/* (ie. any text) q=0.5 */* (ie. any media type)
  • 30.
    OUT OF THEBOX OROCRM REST APIS • $> app/console router:debug | grep api • API Docs via NelmioApiDocBundle, ie. “/api/doc/“ • Enduser friendly API overview • Includes a sandbox to try out API calls • Information is extracted from Annotations on the Controllers
  • 33.
    USING OROCRM RESTAPIS • Constructing the URL for: api/rest/{version}/accounts/{id}.{_format} • Symfony uses a syntax based on RFC 6570 for templated URI • {version} = “v1” (recommended) or “latest” • {id} = ID of an Account instance • {_format} = lazy content type negotiation, ie. “.json”, or use HTTP header “Accept: application/json”
  • 34.
    http://httpie.org/ HTTPie (pronounced aych-tee-tee-pie) isa command line HTTP client. Its goal is to make CLI interaction with web services as human-friendly as possible. It provides a simple “http” command that allows for sending arbitrary HTTP requests using a simple and natural syntax, and displays colorized output. HTTPie can be used for testing, debugging, and generally interacting with HTTP servers.
  • 35.
    WSSE SECURITY $> http--json http://orocrm.lo/api/rest/v1/accounts HTTP/1.1 401 Unauthorized Cache-Control: no-cache Content-Type: application/json Date: Wed, 22 Jul 2015 12:09:08 GMT Server: Apache/2.4.10 (Unix) PHP/5.6.9 Set-Cookie: CRMID=i45n7g8phauhrfr2h53fh576q1; path=/; HttpOnly Transfer-Encoding: chunked WWW-Authenticate: WSSE realm="Secured API", profile="UsernameToken"
  • 37.
    GENERATING THE WSSEHEADER $> app/console oro:wsse:generate-header c51ef6cb3e87dcc6f077b93f0ceb778d23669364 To use WSSE authentication add following headers to the request: Authorization: WSSE profile="UsernameToken" X-WSSE: UsernameToken Username="admin", PasswordDigest="pgkLCLmWcld9xkTUJ4Rxnj+Ww50=", Nonce="MjU3ZWMzYzI1ZTVmYjg5NA==", Created=“2015-07-22T14:12:27+02:00" http://www.orocrm.com/documentation/index/current/cookbook/how-to-use-wsse-authentication
  • 38.
    READING OROCRM RESTAPIS $> http --json http://orocrm.lo/api/rest/v1/accounts 'X-WSSE: UsernameToken Username="admin", PasswordDigest="pgkLCLmWcld9xkTUJ4Rxnj+Ww50=", Nonce="MjU3ZWMzYzI1ZTVmYjg5NA==", Created=“2015-07-22T14:12:27+02:00"' [ { "contacts": {}, "createdAt": "2015-07-06T09:12:31+00:00", "defaultContact": "Mr. Jerry Coleman", "id": 1, "name": "Life Plan Counselling", "organization": "Liip Test",
  • 39.
    WRITING TO OROCRMREST APIS $> http POST --json http://orocrm.lo/api/rest/v1/accounts/1 'X-WSSE: UsernameToken Username="admin", PasswordDigest="pgkLCLmWcld9xkTUJ4Rxnj+Ww50=", Nonce="MjU3ZWMzYzI1ZTVmYjg5NA==", Created=“2015-07-22T14:12:27+02:00"' < account.json $> cat account.json { "account": { "owner": 2 } }
  • 40.
  • 41.
    APPROACHES TO CREATEA NEW API • OroCRM has uses various different approaches • Long term goal of OroCRM is to provide SOAP and REST via the same code • Available approaches: • Helper methods in Oro Platform RestGetController • Serialization via JMS Serializer or via Symfony core Serializer • Oro Platform EntitySerializer is a work in progress which make it even easier to cover REST and SOAP with the same controller
  • 42.
    BASIC CONTROLLER SETUP <?php
 
 namespaceAcmeBundleCartBundleControllerApiRest;
 
 use FOSRestBundleControllerAnnotationsNamePrefix;
 use FOSRestBundleRoutingClassResourceInterface;
 use OroBundleSoapBundleControllerApiRestRestController;
 
 /**
 * @NamePrefix("acme_api_")
 */
 class CartController extends RestController implements ClassResourceInterface
 {
 .. }
  • 43.
    IMPLEMENTING GETTING ALIST (1/3) /**
 * REST GET list
 *
 * @ApiDoc(
 * description="Get all carts",
 * resource=true
 * )
 * @AclAncestor("orocrm_magento_cart_view")
 *
 * @return JsonResponse
 */
 public function cgetAction()
 {
 /** @var Cart[] $carts */
 $carts = $this->getManager()->getListQueryBuilder()->getQuery()->execute();
 
 return new JsonResponse(
 $this->getPreparedItems($carts, self::$fields),
 Codes::HTTP_OK
 );
 }
  • 44.
    IMPLEMENTING GETTING ALIST (2/3) <?php
 
 namespace AcmeBundleCartBundleControllerApiRest;
 use OroCRMBundleMagentoBundleEntityCart;
 
 use SymfonyComponentHttpFoundationJsonResponse;
 use FOSRestBundleControllerAnnotationsNamePrefix;
 use FOSRestBundleRoutingClassResourceInterface;
 use FOSRestBundleUtilCodes;
 use NelmioApiDocBundleAnnotationApiDoc;
 
 use OroBundleSecurityBundleAnnotationAclAncestor;
 use OroBundleSoapBundleControllerApiRestRestController; class CartController extends RestController implements ClassResourceInterface
 { static $fields = array('id', 'subTotal', 'grandTotal', 'taxAmount', 'customer') ..
 }
  • 45.
    IMPLEMENTING GETTING ALIST (3/3) /**
 * {@inheritdoc}
 */
 public function getManager()
 {
 return $this->get('acme.cart.manager.api');
 }
 
 /**
 * Prepare entity field for serialization
 *
 * @param string $field
 * @param mixed $value
 */
 protected function transformEntityField($field, &$value)
 {
 if ($value instanceof Customer) {
 $value = array('name' => $value->getLastName());
 return;
 }
 parent::transformEntityField($field, $value);
 }
  • 46.
    IMPLEMENTING GETTING AONE ENTITY /**
 * REST GET one
 *
 * @ApiDoc(
 * description="Get one cart",
 * resource=true
 * )
 * @AclAncestor("orocrm_magento_cart_view")
 * @param int $cartId
 *
 * @return JsonResponse
 */
 public function getAction($cartId)
 {
 /** @var Cart $cart */
 $cart = $this->getManager()->find($cartId);
 
 return new JsonResponse(
 $this->getPreparedItem($cart, self::$fields),
 empty($cartId) ? Codes::HTTP_NOT_FOUND : Codes::HTTP_OK
 );
 }
  • 47.
    FULL EXAMPLE <?php
 
 namespace OroCRMBundleMagentoBundleControllerApiRest;
 
 useOroCRMBundleMagentoBundleEntityCustomer;
 use OroCRMBundleMagentoBundleEntityCart;
 
 use SymfonyComponentHttpFoundationJsonResponse;
 
 use FOSRestBundleControllerAnnotationsNamePrefix;
 use FOSRestBundleRoutingClassResourceInterface;
 use FOSRestBundleUtilCodes;
 
 use NelmioApiDocBundleAnnotationApiDoc;
 
 use OroBundleSecurityBundleAnnotationAclAncestor;
 use OroBundleSoapBundleControllerApiRestRestController;
 
 /**
 * @NamePrefix("acme_api_")
 */
 class CartController extends RestController implements ClassResourceInterface
 {
 static $fields = array('id', 'subTotal', 'grandTotal', 'taxAmount', 'customer');
 
 /**
 * REST GET list
 *
 * @ApiDoc(
 * description="Get all carts",
 * resource=true
 * )
 * @AclAncestor("orocrm_magento_cart_view")
 *
 * @return JsonResponse
 */
 public function cgetAction()
 {
 /** @var Cart[] $carts */
 $carts = $this->getManager()->getListQueryBuilder()->getQuery()->execute();
 
 return new JsonResponse(
 $this->getPreparedItems($carts, self::$fields),
 Codes::HTTP_OK
 );
 }
 /**
 * REST GET one
 *
 * @ApiDoc(
 * description="Get one cart",
 * resource=true
 * )
 * @AclAncestor("orocrm_magento_cart_view")
 * @param int $cartId
 *
 * @return JsonResponse
 */
 public function getAction($cartId)
 {
 /** @var Cart $cart */
 $cart = $this->getManager()->find($cartId);
 
 return new JsonResponse(
 $this->getPreparedItem($cart, self::$fields),
 empty($cartId) ? Codes::HTTP_NOT_FOUND : Codes::HTTP_OK
 );
 }
 
 public function getManager()
 {
 return $this->get('orocrm_magento.cart.manager.api');
 }
 
 protected function transformEntityField($field, &$value)
 {
 if ($value instanceof Customer) {
 $value = array('name' => $value->getLastName());
 return;
 }
 parent::transformEntityField($field, $value);
 }
 
 public function getFormHandler()
 {
 throw new BadMethodCallException('FormHandler is not available.');
 }
 }

  • 48.
    REQUIRED SERVICE # services.yml parameters: acme.cart.manager.api.class:OroBundleSoapBundle..ApiEntityManager
 services: acme.cart.manager.api:
 class: %acme.cart.manager.api.class%
 parent: oro_soap.manager.entity_manager.abstract
 arguments:
 - %orocrm_magento.entity.cart.class%
 - @doctrine.orm.entity_manager
  • 49.
    REQUIRED ROUTE DEFINITION #routing.yml acme_bundle_cart_api:
 resource: "@AcmeCartBundle/Controller/Api/Rest/CartController.php"
 type: rest
 prefix: api/rest/{version}
 requirements:
 version: latest|v1
 defaults:
 version: latest
  • 50.
    USING OUR NEWOROCRM REST API $> http --json http://orocrm.lo/api/rest/v1/carts ‘X-WSSE..’ .. [ { "customer": { "name": "Clark" }, "grandTotal": "163.9248", "id": 1, "subTotal": "163.9248", "taxAmount": "12.6748" },
  • 51.
    TESTING OROCRM RESTAPIS (1/2) use OroBundleTestFrameworkBundleTestWebTestCase;
 
 class ApiCartControllerTest extends WebTestCase
 {
 /* Taken from BazingaRestExtraBundle */
 protected function assertJsonResponse($response, $statusCode = 200)
 {
 $this->assertEquals(
 $statusCode, $response->getStatusCode(),
 $response->getContent()
 );
 $this->assertTrue(
 $response->headers->contains('Content-Type', 'application/json'),
 $response->headers
 );
 }
 
 protected function setUp()
 {
 $this->initClient(array(), $this->generateWsseAuthHeader());
 } ..

  • 52.
    TESTING OROCRM RESTAPIS (2/2) public function testGetCarts()
 {
 $this->client->request('HEAD', '/api/rest/v1/carts');
 $response = $this->client->getResponse();
 $this->assertEquals(200, $response->getStatusCode(), $response->getContent());
 
 $this->client->request('GET', ‘/api/rest/v1/carts');
 $response = $this->client->getResponse();
 
 $this->assertJsonResponse($response);
 $this->assertEquals(‘..’, $response->getContent());
 }
 
 public function testGetCart()
 {
 $this->client->request('GET', '/api/rest/v1/carts/1');
 $response = $this->client->getResponse();
 
 $this->assertJsonResponse($response);
 $this->assertEquals('{"id": 1,"subTotal":"163.9248","grandTotal":"163.9248","taxAmount":"12.6748","customer": {"name":"Clark"}}', $response->getContent());
 }
  • 53.
  • 54.