Slide 1

Slide 1 text

Agile Web Development Liip.ch – RESTING WITH Learn how to use and extend the OroCRM REST API

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

INTRODUCTION TO REST

Slide 4

Slide 4 text

Everyone who has ever talked about REST, at some point has said something idiotic about REST. Except for maybe Roy Fielding.

Slide 5

Slide 5 text

For example, I once thought it meant converting all GET parameters to virtual directories. Differentiating GET and POST seemed like overzealous academics.

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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"]}

Slide 10

Slide 10 text

REST MATURITY MODEL http://martinfowler.com/articles/richardsonMaturityModel.html

Slide 11

Slide 11 text

RMM LEVEL 0 • Aka "The Swamp of POX" • HTTP as a tunneling mechanism • "Procedural" communication (RPC) • Single endpoint (per operation)

Slide 12

Slide 12 text

RMM LEVEL 0 http://martinfowler.com/articles/richardsonMaturityModel.html

Slide 13

Slide 13 text

RMM LEVEL 1 • Aka "Resources" • Individual resources, i.e. URIs • "Object orientated" communication

Slide 14

Slide 14 text

RMM LEVEL 1 http://martinfowler.com/articles/richardsonMaturityModel.html

Slide 15

Slide 15 text

RMM LEVEL 2 • Aka "HTTP Verbs" • Client uses specific HTTP method • Server uses HTTP status codes

Slide 16

Slide 16 text

RMM LEVEL 2 http://martinfowler.com/articles/richardsonMaturityModel.html

Slide 17

Slide 17 text

HTTP VERBS Method Safe Idempotent GET yes yes HEAD yes yes POST no no PUT no yes DELETE no yes (*) .. no no

Slide 18

Slide 18 text

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?

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

RMM LEVEL 3 • Aka "Hypermedia Control" • Service discovery via link relations • ATOM, HAL, JSON-LD, IANA Link Rel

Slide 21

Slide 21 text

RMM LEVEL 3 http://martinfowler.com/articles/richardsonMaturityModel.html

Slide 22

Slide 22 text

HYPERTEXT AS THE ENGINE OF APPLICATION STATE = HATEOAS

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

RMM VS REST VS REAL LIFE • Browsers are bad REST clients • REST is protocol independent • Few (no?) clients really leverage HATEOAS

Slide 25

Slide 25 text

CONTENT TYPE NEGOTIATION

Slide 26

Slide 26 text

UNIFIED RESOURCE IDENTIFIER • URIs identify resources • URIs are format independent • URI "file extensions" != RESTful

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

• 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

Slide 29

Slide 29 text

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)

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

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”

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

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"

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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",

Slide 39

Slide 39 text

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 } }

Slide 40

Slide 40 text

DEMO TIME

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

BASIC CONTROLLER SETUP

Slide 43

Slide 43 text

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
 );
 }

Slide 44

Slide 44 text

IMPLEMENTING GETTING A LIST (2/3)

Slide 45

Slide 45 text

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);
 }

Slide 46

Slide 46 text

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
 );
 }

Slide 47

Slide 47 text

FULL EXAMPLE 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.');
 }
 }


Slide 48

Slide 48 text

REQUIRED SERVICE # services.yml parameters: acme.cart.manager.api.class: Oro\Bundle\SoapBundle\..\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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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" },

Slide 51

Slide 51 text

TESTING OROCRM REST APIS (1/2) use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase;
 
 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());
 } ..


Slide 52

Slide 52 text

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());
 }

Slide 53

Slide 53 text

DEMO TIME

Slide 54

Slide 54 text

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