a unified soap/json api with symfony2
TRANSCRIPT
A UNIFIED SOAP / JSON APIusing Symfony2
Tuesday, 12 June 12
ABOUT ME
• Craig Marvelley
•Developer at Box UK
•@craigmarvelley
• Using Symfony for ~ 1 year
Tuesday, 12 June 12
WEBSITE
LEGACY APIS(SOAP)
MOBILE DEVICES
THE PROBLEM
Tuesday, 12 June 12
WEBSITE
LEGACY APIS(SOAP)
MOBILE DEVICES
THE SOLUTION
FACADE API(SOAP/JSON)
SOAP
JSON
SOAP
Tuesday, 12 June 12
Request
JSONController SOAPController
ProcessingService
JSON Response
/soap/json
SOAP Response
Tuesday, 12 June 12
KEY COMPONENTS
• Individual request classes to encapsulate data
• A custom ParamConverter creates objects from JSON requests
•Objects created from SOAP requests according to WSDL
•WebserviceManager class performs processing and creates an individual response object
• Response is returned to appropriate controller, and output
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Request;
use BoxUK\Bundle\ApiBundle\Request;use Symfony\Component\Validator\Constraints as Assert;use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;
/** * @Assert\Callback(methods={ * { "BoxUK\Bundle\ApiBundle\Request\Validator", "isValidDomainName"} * }) */class DomainInfoRequest extends AbstractRequest {
/** * @Assert\NotBlank() * @Soap\ComplexType("string") */ private $domainName;
public function setDomainName( $domainName ) { $this->domainName = $domainName; }
public function getDomainName() { return $this->domainName; }
}
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Request;
use BoxUK\Bundle\ApiBundle\Request;use Symfony\Component\Validator\Constraints as Assert;use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;
/** * @Assert\Callback(methods={ * { "BoxUK\Bundle\ApiBundle\Request\Validator", "isValidDomainName"} * }) */class DomainInfoRequest extends AbstractRequest {
/** * @Assert\NotBlank() * @Soap\ComplexType("string") */ private $domainName;
public function setDomainName( $domainName ) { $this->domainName = $domainName; }
public function getDomainName() { return $this->domainName; }
}
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Request;
use BoxUK\Bundle\ApiBundle\Request;use Symfony\Component\Validator\Constraints as Assert;use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;
/** * @Assert\Callback(methods={ * { "BoxUK\Bundle\ApiBundle\Request\Validator", "isValidDomainName"} * }) */class DomainInfoRequest extends AbstractRequest {
/** * @Assert\NotBlank() * @Soap\ComplexType("string") */ private $domainName;
public function setDomainName( $domainName ) { $this->domainName = $domainName; }
public function getDomainName() { return $this->domainName; }
}
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;use BoxUK\Bundle\ApiBundle\Request\DomainInfoRequest;
/** * @Route("/json") */class JsonController extends Controller { /** * @Route("/domainInfo") * @Method("GET") */ public function domainInfoAction(DomainInfoRequest $request) { return $this->respond( $this->getManager()->domainInfo( $request ) ); }
/** * @return \BoxUK\Bundle\ApiBundle\Management\WebserviceManager */protected function getManager() { return $this->container->get( 'box_uk.api.webservice_manager' );}
....}
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Controller;use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;use Symfony\Component\DependencyInjection\ContainerAware;
class SoapController extends ContainerAware { /** * @Soap\Method("domainInfo") * @Soap\Param("request", phpType = "BoxUK\Bundle\ApiBundle\Request\DomainInfoRequest") * @Soap\Result(phpType = "BoxUK\Bundle\ApiBundle\Response\DomainInfoRequest") */ public function domainInfoAction(DomainInfoRequest $request) { $response = $this->getManager()->domainInfo($request); return $this->respond($response); }
....}
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Controller;use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;use Symfony\Component\DependencyInjection\ContainerAware;
class SoapController extends ContainerAware { /** * @Soap\Method("domainInfo") * @Soap\Param("request", phpType = "BoxUK\Bundle\ApiBundle\Request\DomainInfoRequest") * @Soap\Result(phpType = "BoxUK\Bundle\ApiBundle\Response\DomainInfoRequest") */ public function domainInfoAction(DomainInfoRequest $request) { $response = $this->getManager()->domainInfo($request); return $this->respond($response); }
....}
Tuesday, 12 June 12
<?php
namespace BoxUK\Bundle\ApiBundle\Controller;use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;use Symfony\Component\DependencyInjection\ContainerAware;
class SoapController extends ContainerAware { /** * @param \BoxUK\Bundle\ApiBundle\Response $response * @return mixed */ protected function respond( $response ) {
if ( !$response->getSuccess() ) { $code = $response->getCode(); throw new \SoapFault(
$faultcode, $response->getErrorMessage(), null, $response->getErrorCode() );
}
return $this->getSoapResponse()->setReturnValue( $response ); }}
Tuesday, 12 June 12
WEBSERVICE MANAGER
• Registered as a service in services.xml
• Injected into both JSON and SOAP controllers
• Validates request content according to annotations
• Handles communication with legacy webservice API
• Uses Monolog for fine-grained logging (error & activity)
• Uses Doctrine2 to access and persist data
• Constructs responsesTuesday, 12 June 12
HANDY SYMFONY2 FEATURES
• Used a custom annotation serializer to transform objects into JSON
• Used a ParamConverter to transform Symfony Request into agnostic Request objects (JSON only)
• Used a kernel listener to automatically validate user’s access key (JSON only)
• Used commands with a crontab to perform periodic updates to the database
Tuesday, 12 June 12
COOL BUNDLES
• BeSimpleSoapBundle - Provides SOAP integration for Symfony2, automatically serialize/deserialise data to objects. USES ZEND\SOAP!
• LiipFunctionalTestBundle - Enhanced functional tests, database caching
•DoctrineFixturesBundle - For maintaining test data for functional tests
•DoctrineMigrationsBundle - For versioning the database schema
Tuesday, 12 June 12
TESTING
• Lots and lots of unit tests
• Functional tests for controller actions
• Used a developer-in-test
• He used SoapUI to create test cases
• Automated SOAP request/responses from WSDL
Tuesday, 12 June 12
THANKS FOR LISTENING!https://joind.in/talk/view/6667
@craigmarvelley
Tuesday, 12 June 12