Join us we partner with Liip to bring you our webinar, “Resting with OroCRM”, which will demonstrate how to leverage REST with OroCRM. Liip is one of Switzerland’s leading developers of tailor-made, agile web applications and has a deep understanding of REST API’s. During this webinar, we will introduce viewers to REST and demonstrate how to use and extend OroCRM’s out-of-the-box REST API’s.
The agenda for our webinar will be as follows:
–Introduce viewers to REST theory, explaining the REST maturity model and content type negotiation
–Show how to discover which REST API’s OroCRM has to offer, how to generate API tokens and how to use them to read/write data from and into OroCRM
–Demonstrate how to add a new REST API to OroCRM by adding a new REST API to list shopping cart data imported via our Magento connector
4. Everyone who has ever talked
about REST, at some point has said
something idiotic about REST.
Except for maybe Roy Fielding.
5. For example, I once thought it
meant converting all GET
parameters to virtual directories.
Differentiating GET and POST
seemed like overzealous
academics.
6. 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.
7. 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.
17. HTTP VERBS
Method Safe Idempotent
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
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
20. RMM LEVEL 3
• Aka "Hypermedia Control"
• Service discovery via link relations
• ATOM, HAL, JSON-LD, IANA Link Rel
23. 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
24. RMM VS REST VS REAL LIFE
• Browsers are bad REST clients
• REST is protocol independent
• Few (no?) clients really leverage HATEOAS
26. UNIFIED RESOURCE IDENTIFIER
• URIs identify resources
• URIs are format independent
• URI "file extensions" != RESTful
27. 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
28. • 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
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 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
31.
32.
33. 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”
34. 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.
37. 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
41. 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
42. BASIC CONTROLLER SETUP
<?php
namespace AcmeBundleCartBundleControllerApiRest;
use FOSRestBundleControllerAnnotationsNamePrefix;
use FOSRestBundleRoutingClassResourceInterface;
use OroBundleSoapBundleControllerApiRestRestController;
/**
* @NamePrefix("acme_api_")
*/
class CartController extends RestController implements ClassResourceInterface
{
..
}
43. 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
);
}
44. 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')
..
}
45. 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);
}
46. 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
);
}
47. 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.');
}
}