Created
September 29, 2012 22:48
-
-
Save jmather/3805361 to your computer and use it in GitHub Desktop.
RESTful Versioned API with Silex using Accept header
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use Silex\WebTestCase; | |
| use Symfony\Component\HttpKernel\HttpKernel; | |
| use Silex\Controller; | |
| use Silex\ControllerCollection; | |
| use Symfony\Component\HttpFoundation\Response; | |
| use Symfony\Component\HttpFoundation\Request; | |
| use Symfony\Component\HttpKernel\KernelEvents; | |
| class AcceptHeaderRoutingTest extends WebTestCase | |
| { | |
| /** | |
| * Creates the application. | |
| * | |
| * @return HttpKernel | |
| */ | |
| public function createApplication() | |
| { | |
| $app = new \Silex\Application(); | |
| $app['dispatcher']->addSubscriber(new \VersionedRestKernelListener()); | |
| $app['versioned_rest_controllers_factory'] = $app->protect(function ($version) use ($app) { | |
| return new \VersionedRestControllerCollection($app['route_factory'], $version); | |
| }); | |
| $app['url_matcher'] = $app->share(function () use ($app) { | |
| return new \VersionedRestUrlMatcher($app['routes'], $app['request_context'], $app['request']); | |
| }); | |
| /** @var $controllers1 VersionedRestControllerCollection */ | |
| $controllers1 = $app['versioned_rest_controllers_factory']('application/ven.test.v1'); | |
| $controllers1->get('/test', function(Request $request) use ($app) { | |
| $_format = $request->request->get('_format'); | |
| $_api_version = $request->request->get('_api_version'); | |
| if ($_format == 'json') | |
| $cont = json_encode(array('content' => 'hello')); | |
| else | |
| $cont = '<content>hello</content>'; | |
| return new Response($cont, 200, array('Content-Type' => $_api_version.'+'.$_format)); | |
| }, array('json', 'xml')); | |
| /** @var $controllers1 VersionedRestControllerCollection */ | |
| $controllers2 = $app['versioned_rest_controllers_factory']('application/ven.test.v2'); | |
| $controllers2->get('/test', function(Request $request) use ($app) { | |
| $_format = $request->request->get('_format'); | |
| $_api_version = $request->request->get('_api_version'); | |
| if ($_format == 'json') | |
| $cont = json_encode(array('content' => 'hiya')); | |
| elseif ($_format == 'xml') | |
| $cont = '<content>hiya</content>'; | |
| return new Response($cont, 200, array('Content-Type' => $_api_version.'+'.$_format)); | |
| }, array('xml', 'json')); | |
| $app->mount('/', $controllers1); | |
| $app->mount('/', $controllers2); | |
| $app['debug'] = true; | |
| unset($app['exception_handler']); | |
| return $app; | |
| } | |
| public function testValidV1Call() | |
| { | |
| $client = $this->createClient(); | |
| $crawler = $client->request('GET', '/test', array(), array(), array('HTTP_ACCEPT' => 'application/ven.test.v1+xml')); | |
| $this->assertEquals(200, $client->getResponse()->getStatusCode()); | |
| $result = $client->getResponse()->getContent(); | |
| $this->assertEquals('<content>hello</content>', $result, 'response is correct'); | |
| } | |
| public function testValidV2Call() | |
| { | |
| $client = $this->createClient(); | |
| $crawler = $client->request('GET', '/test', array(), array(), array('HTTP_ACCEPT' => 'application/ven.test.v2+xml')); | |
| $this->assertEquals(200, $client->getResponse()->getStatusCode()); | |
| $result = $client->getResponse()->getContent(); | |
| $this->assertEquals('<content>hiya</content>', $result, 'response is correct'); | |
| } | |
| /** | |
| * @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException | |
| */ | |
| public function testInvalidV3Call() | |
| { | |
| $client = $this->createClient(); | |
| $crawler = $client->request('GET', '/test', array(), array(), array('HTTP_ACCEPT' => 'application/ven.test.v3+xml')); | |
| $this->assertEquals(404, $client->getResponse()->getStatusCode()); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use Silex\Controller; | |
| use Silex\Route; | |
| class VersionedRestControllerCollection extends \Silex\ControllerCollection | |
| { | |
| private $accept_header; | |
| /** | |
| * Constructor. | |
| */ | |
| public function __construct(Route $defaultRoute, $accept_header) | |
| { | |
| $this->defaultRoute = $defaultRoute; | |
| $this->accept_header = $accept_header; | |
| } | |
| /** | |
| * Maps a pattern to a callable. | |
| * | |
| * You can optionally specify HTTP methods that should be matched. | |
| * | |
| * @param string $pattern Matched route pattern | |
| * @param mixed $to Callback that returns the response when matched | |
| * @param array $formats | |
| * | |
| * @return Controller | |
| */ | |
| public function match($pattern, $to, $formats = array()) | |
| { | |
| if (!is_array($formats)) | |
| $formats = array($formats); | |
| $route = clone $this->defaultRoute; | |
| $route->setPattern($pattern); | |
| $route->setDefault('_controller', $to); | |
| $route->addRequirements(array('_format' => '('.implode('|', $formats).')')); | |
| $route->addRequirements(array('_accept' => preg_quote($this->accept_header, '/'))); | |
| $this->controllers[] = $controller = new Controller($route); | |
| $controller->bind(md5($this->accept_header.'_'.$pattern)); | |
| return $controller; | |
| } | |
| /** | |
| * Maps a GET request to a callable. | |
| * | |
| * @param string $pattern Matched route pattern | |
| * @param mixed $to Callback that returns the response when matched | |
| * @param array $formats | |
| * | |
| * @return Controller | |
| */ | |
| public function get($pattern, $to, $formats = array()) | |
| { | |
| return $this->match($pattern, $to, $formats)->method('GET'); | |
| } | |
| /** | |
| * Maps a POST request to a callable. | |
| * | |
| * @param string $pattern Matched route pattern | |
| * @param mixed $to Callback that returns the response when matched | |
| * @param array $formats | |
| * | |
| * @return Controller | |
| */ | |
| public function post($pattern, $to, $formats = array()) | |
| { | |
| return $this->match($pattern, $to, $formats)->method('POST'); | |
| } | |
| /** | |
| * Maps a PUT request to a callable. | |
| * | |
| * @param string $pattern Matched route pattern | |
| * @param mixed $to Callback that returns the response when matched | |
| * @param array $formats | |
| * | |
| * @return Controller | |
| */ | |
| public function put($pattern, $to, $formats = array()) | |
| { | |
| return $this->match($pattern, $to, $formats)->method('PUT'); | |
| } | |
| /** | |
| * Maps a DELETE request to a callable. | |
| * | |
| * @param string $pattern Matched route pattern | |
| * @param mixed $to Callback that returns the response when matched | |
| * @param array $formats | |
| * | |
| * @return Controller | |
| */ | |
| public function delete($pattern, $to, $formats = array()) | |
| { | |
| return $this->match($pattern, $to, $formats)->method('DELETE'); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use Symfony\Component\HttpKernel\Event\FilterControllerEvent; | |
| use Symfony\Component\HttpKernel\HttpKernelInterface; | |
| use Symfony\Component\HttpKernel\Event\GetResponseEvent; | |
| use Symfony\Component\HttpKernel\KernelEvents; | |
| use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
| class VersionedRestKernelListener implements EventSubscriberInterface | |
| { | |
| public static function onKernelRequest(GetResponseEvent $event) | |
| { | |
| $pattern = '|^(.*)(?:\+([a-z]+))?$|U'; | |
| if (preg_match($pattern, $event->getRequest()->headers->get('Accept'), $matches)) | |
| { | |
| if (isset($matches[2]) && $matches[2] != '') | |
| $event->getRequest()->request->set('_format', $matches[2]); | |
| $event->getRequest()->request->set('_accept', $matches[1]); | |
| } | |
| } | |
| /** | |
| * Returns an array of event names this subscriber wants to listen to. | |
| * | |
| * The array keys are event names and the value can be: | |
| * | |
| * * The method name to call (priority defaults to 0) | |
| * * An array composed of the method name to call and the priority | |
| * * An array of arrays composed of the method names to call and respective | |
| * priorities, or 0 if unset | |
| * | |
| * For instance: | |
| * | |
| * * array('eventName' => 'methodName') | |
| * * array('eventName' => array('methodName', $priority)) | |
| * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) | |
| * | |
| * @return array The event names to listen to | |
| * | |
| * @api | |
| */ | |
| public static function getSubscribedEvents() | |
| { | |
| return array(KernelEvents::REQUEST => array('onKernelRequest', 100)); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use Symfony\Component\Routing\Route; | |
| use Symfony\Component\Routing\RouteCollection; | |
| use Symfony\Component\Routing\RequestContext; | |
| use Symfony\Component\HttpFoundation\Request; | |
| class VersionedRestUrlMatcher extends Silex\RedirectableUrlMatcher | |
| { | |
| private $request; | |
| public function __construct(RouteCollection $routes, RequestContext $context, Request $request) | |
| { | |
| parent::__construct($routes, $context); | |
| $this->request = $request; | |
| } | |
| protected function handleRouteRequirements($pathinfo, $name, Route $route) | |
| { | |
| $ret = parent::handleRouteRequirements($pathinfo, $name, $route); | |
| if ($ret[0] == self::REQUIREMENT_MISMATCH) | |
| return $ret; | |
| foreach($route->getRequirements() as $name => $value) | |
| { | |
| if ($name == '_method') | |
| continue; | |
| if (false == preg_match('/^'.$value.'$/', $this->request->request->get($name))) | |
| return array(self::REQUIREMENT_MISMATCH, null); | |
| } | |
| return array(self::REQUIREMENT_MATCH, null); | |
| } | |
| } |
Author
Hah! Thanks. So caught up in the process, I missed something silly-simple.
you should also return something always
return;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You can replace:
With: