KEMBAR78
A Unified SOAP/JSON API with Symfony2 | PDF
A UNIFIED SOAP / JSON API
                      using Symfony2




Tuesday, 12 June 12
ABOUT ME


    • Craig           Marvelley

    • Developer           at Box UK

    • @craigmarvelley

    • Using           Symfony for ~ 1 year



Tuesday, 12 June 12
THE PROBLEM

                      WEBSITE


                                              LEGACY APIS
                                                (SOAP)


         MOBILE DEVICES



Tuesday, 12 June 12
THE SOLUTION

                                SOAP
                      WEBSITE


                                        FACADE API   SOAP   LEGACY APIS
                                       (SOAP/JSON)            (SOAP)

                                JSON
         MOBILE DEVICES



Tuesday, 12 June 12
Request
                           /json                  /soap




              JSONController                       SOAPController




                                   Processing
                                    Service



                      JSON Response                       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 BoxUKBundleApiBundleRequest;

use BoxUKBundleApiBundleRequest;
use SymfonyComponentValidatorConstraints as Assert;
use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap;

/**
 * @AssertCallback(methods={
 *     { "BoxUKBundleApiBundleRequestValidator", "isValidDomainName"}
 * })
 */
class DomainInfoRequest extends AbstractRequest {

        /**
         * @AssertNotBlank()
         * @SoapComplexType("string")
         */
        private $domainName;

        public function setDomainName( $domainName ) {
            $this->domainName = $domainName;
        }

        public function getDomainName() {
            return $this->domainName;
        }

}

Tuesday, 12 June 12
<?php

namespace BoxUKBundleApiBundleRequest;

use BoxUKBundleApiBundleRequest;
use SymfonyComponentValidatorConstraints as Assert;
use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap;

/**
 * @AssertCallback(methods={
 *     { "BoxUKBundleApiBundleRequestValidator", "isValidDomainName"}
 * })
 */
class DomainInfoRequest extends AbstractRequest {

        /**
         * @AssertNotBlank()
         * @SoapComplexType("string")
         */
        private $domainName;

        public function setDomainName( $domainName ) {
            $this->domainName = $domainName;
        }

        public function getDomainName() {
            return $this->domainName;
        }

}

Tuesday, 12 June 12
<?php

namespace BoxUKBundleApiBundleRequest;

use BoxUKBundleApiBundleRequest;
use SymfonyComponentValidatorConstraints as Assert;
use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap;

/**
 * @AssertCallback(methods={
 *     { "BoxUKBundleApiBundleRequestValidator", "isValidDomainName"}
 * })
 */
class DomainInfoRequest extends AbstractRequest {

        /**
         * @AssertNotBlank()
         * @SoapComplexType("string")
         */
        private $domainName;

        public function setDomainName( $domainName ) {
            $this->domainName = $domainName;
        }

        public function getDomainName() {
            return $this->domainName;
        }

}

Tuesday, 12 June 12
<?php

namespace BoxUKBundleApiBundleController;

use SymfonyBundleFrameworkBundleControllerController;
use BoxUKBundleApiBundleRequestDomainInfoRequest;

/**
  * @Route("/json")
  */
class JsonController extends Controller
{
     /**
       * @Route("/domainInfo")
       * @Method("GET")
       */
     public function domainInfoAction(DomainInfoRequest $request) {
          return $this->respond( $this->getManager()->domainInfo( $request ) );
     }

         /**
           * @return BoxUKBundleApiBundleManagementWebserviceManager
           */
         protected function getManager() {
              return $this->container->get( 'box_uk.api.webservice_manager' );
         }

         ....
}


Tuesday, 12 June 12
<?php

namespace BoxUKBundleApiBundleController;
use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap;
use SymfonyComponentDependencyInjectionContainerAware;

class SoapController extends ContainerAware
{
    /**
      * @SoapMethod("domainInfo")
      * @SoapParam("request", phpType = "BoxUKBundleApiBundleRequest
DomainInfoRequest")
      * @SoapResult(phpType = "BoxUKBundleApiBundleResponseDomainInfoRequest")
      */
    public function domainInfoAction(DomainInfoRequest $request)
    {
         $response = $this->getManager()->domainInfo($request);
         return $this->respond($response);
    }

        ....
}




Tuesday, 12 June 12
<?php

namespace BoxUKBundleApiBundleController;
use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap;
use SymfonyComponentDependencyInjectionContainerAware;

class SoapController extends ContainerAware
{
    /**
      * @SoapMethod("domainInfo")
      * @SoapParam("request", phpType = "BoxUKBundleApiBundleRequest
DomainInfoRequest")
      * @SoapResult(phpType = "BoxUKBundleApiBundleResponseDomainInfoRequest")
      */
    public function domainInfoAction(DomainInfoRequest $request)
    {
         $response = $this->getManager()->domainInfo($request);
         return $this->respond($response);
    }

        ....
}




Tuesday, 12 June 12
<?php

namespace BoxUKBundleApiBundleController;
use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap;
use SymfonyComponentDependencyInjectionContainerAware;

class SoapController extends ContainerAware
{
    /**
     * @param BoxUKBundleApiBundleResponse $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            responses
Tuesday, 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 ZENDSOAP!

    • 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

A Unified SOAP/JSON API with Symfony2

  • 1.
    A UNIFIED SOAP/ JSON API using Symfony2 Tuesday, 12 June 12
  • 2.
    ABOUT ME • Craig Marvelley • Developer at Box UK • @craigmarvelley • Using Symfony for ~ 1 year Tuesday, 12 June 12
  • 3.
    THE PROBLEM WEBSITE LEGACY APIS (SOAP) MOBILE DEVICES Tuesday, 12 June 12
  • 4.
    THE SOLUTION SOAP WEBSITE FACADE API SOAP LEGACY APIS (SOAP/JSON) (SOAP) JSON MOBILE DEVICES Tuesday, 12 June 12
  • 5.
    Request /json /soap JSONController SOAPController Processing Service JSON Response SOAP Response Tuesday, 12 June 12
  • 6.
    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
  • 7.
    <?php namespace BoxUKBundleApiBundleRequest; use BoxUKBundleApiBundleRequest; useSymfonyComponentValidatorConstraints as Assert; use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap; /** * @AssertCallback(methods={ * { "BoxUKBundleApiBundleRequestValidator", "isValidDomainName"} * }) */ class DomainInfoRequest extends AbstractRequest { /** * @AssertNotBlank() * @SoapComplexType("string") */ private $domainName; public function setDomainName( $domainName ) { $this->domainName = $domainName; } public function getDomainName() { return $this->domainName; } } Tuesday, 12 June 12
  • 8.
    <?php namespace BoxUKBundleApiBundleRequest; use BoxUKBundleApiBundleRequest; useSymfonyComponentValidatorConstraints as Assert; use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap; /** * @AssertCallback(methods={ * { "BoxUKBundleApiBundleRequestValidator", "isValidDomainName"} * }) */ class DomainInfoRequest extends AbstractRequest { /** * @AssertNotBlank() * @SoapComplexType("string") */ private $domainName; public function setDomainName( $domainName ) { $this->domainName = $domainName; } public function getDomainName() { return $this->domainName; } } Tuesday, 12 June 12
  • 9.
    <?php namespace BoxUKBundleApiBundleRequest; use BoxUKBundleApiBundleRequest; useSymfonyComponentValidatorConstraints as Assert; use BeSimpleSoapBundleServiceDefinitionAnnotation as Soap; /** * @AssertCallback(methods={ * { "BoxUKBundleApiBundleRequestValidator", "isValidDomainName"} * }) */ class DomainInfoRequest extends AbstractRequest { /** * @AssertNotBlank() * @SoapComplexType("string") */ private $domainName; public function setDomainName( $domainName ) { $this->domainName = $domainName; } public function getDomainName() { return $this->domainName; } } Tuesday, 12 June 12
  • 10.
    <?php namespace BoxUKBundleApiBundleController; use SymfonyBundleFrameworkBundleControllerController; useBoxUKBundleApiBundleRequestDomainInfoRequest; /** * @Route("/json") */ class JsonController extends Controller { /** * @Route("/domainInfo") * @Method("GET") */ public function domainInfoAction(DomainInfoRequest $request) { return $this->respond( $this->getManager()->domainInfo( $request ) ); } /** * @return BoxUKBundleApiBundleManagementWebserviceManager */ protected function getManager() { return $this->container->get( 'box_uk.api.webservice_manager' ); } .... } Tuesday, 12 June 12
  • 11.
    <?php namespace BoxUKBundleApiBundleController; use BeSimpleSoapBundleServiceDefinitionAnnotationas Soap; use SymfonyComponentDependencyInjectionContainerAware; class SoapController extends ContainerAware { /** * @SoapMethod("domainInfo") * @SoapParam("request", phpType = "BoxUKBundleApiBundleRequest DomainInfoRequest") * @SoapResult(phpType = "BoxUKBundleApiBundleResponseDomainInfoRequest") */ public function domainInfoAction(DomainInfoRequest $request) { $response = $this->getManager()->domainInfo($request); return $this->respond($response); } .... } Tuesday, 12 June 12
  • 12.
    <?php namespace BoxUKBundleApiBundleController; use BeSimpleSoapBundleServiceDefinitionAnnotationas Soap; use SymfonyComponentDependencyInjectionContainerAware; class SoapController extends ContainerAware { /** * @SoapMethod("domainInfo") * @SoapParam("request", phpType = "BoxUKBundleApiBundleRequest DomainInfoRequest") * @SoapResult(phpType = "BoxUKBundleApiBundleResponseDomainInfoRequest") */ public function domainInfoAction(DomainInfoRequest $request) { $response = $this->getManager()->domainInfo($request); return $this->respond($response); } .... } Tuesday, 12 June 12
  • 13.
    <?php namespace BoxUKBundleApiBundleController; use BeSimpleSoapBundleServiceDefinitionAnnotationas Soap; use SymfonyComponentDependencyInjectionContainerAware; class SoapController extends ContainerAware { /** * @param BoxUKBundleApiBundleResponse $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
  • 14.
    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 responses Tuesday, 12 June 12
  • 15.
    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
  • 16.
    COOL BUNDLES • BeSimpleSoapBundle - Provides SOAP integration for Symfony2, automatically serialize/deserialise data to objects. USES ZENDSOAP! • LiipFunctionalTestBundle - Enhanced functional tests, database caching • DoctrineFixturesBundle - For maintaining test data for functional tests • DoctrineMigrationsBundle - For versioning the database schema Tuesday, 12 June 12
  • 17.
    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
  • 18.
    THANKS FOR LISTENING! https://joind.in/talk/view/6667 @craigmarvelley Tuesday, 12 June 12