Silex meets SOAP & REST

62
Hugo Hamon @hhamon

description

Silex is a brand new PHP 5.3 micro framework built on top of the Symfony2 de decoupled components. In this session, we will discover how to build and deploy powerful REST web services with such a micro framework and its embedded tools. The first part of this talk will introduce the basics of the REST architecture. We fill focus on the main concepts of REST like HTTP methods, URIs and open formats like XML and JSON. Then, we will discover how to deploy REST services using most of interesting Silex tools like database abstraction layer, template engine and input validation. We will also look at unit and functional testing frameworks with PHPUnit and HTTP caching with Edge Side Includes and Varnish support to improve performances.

Transcript of Silex meets SOAP & REST

Page 1: Silex meets SOAP & REST

Hugo Hamon @hhamon

Page 2: Silex meets SOAP & REST

Silex meets REST and SOAP

Page 3: Silex meets SOAP & REST

What is Silex?

Page 4: Silex meets SOAP & REST
Page 5: Silex meets SOAP & REST
Page 6: Silex meets SOAP & REST
Page 7: Silex meets SOAP & REST

http://silex.sensiolabs.org

Page 8: Silex meets SOAP & REST

Why choosing Silex?

Page 9: Silex meets SOAP & REST

What’s inside?

Page 10: Silex meets SOAP & REST

The Silex Philosophy

Page 11: Silex meets SOAP & REST

silex.phar

cache/

logs/

src/

tests/

vendor/

web/

Page 12: Silex meets SOAP & REST

Silex Mantra

namespace Symfony\Component\HttpKernel;

interface HttpKernelInterface

{

(Response) function handle(Request $request);

}

Page 13: Silex meets SOAP & REST

Request Handling

require_once __DIR__.'/silex.phar'; $app = new Silex\Application(); $app->get('/hello/{name}', function($name) use($app) { return 'Hello '. $app->escape($name); }); $app->run();

Page 14: Silex meets SOAP & REST

Request Handling

require_once __DIR__.'/silex.phar';

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

$app = new Silex\Application();

$app->get('/hello/{name}', function(Request $request) use($app) {

$name = $request->attributes->get('name');

return new Response('Hello '. $app->escape($name));

});

Page 15: Silex meets SOAP & REST

$app == DIC

Page 16: Silex meets SOAP & REST

Deploying REST Web services

Page 17: Silex meets SOAP & REST

REpresentational State Transfer

Page 18: Silex meets SOAP & REST

Architecture style

Page 19: Silex meets SOAP & REST

Designing REST APIs

Page 20: Silex meets SOAP & REST

$app->get('/events', function (Request $request) { $events = array( array('name' => 'OSIDays', 'venue' => 'Bangalore'), array('name' => 'PHP Tour', 'venue' => 'Lille'), array('name' => 'Confoo', 'venue' => 'Montreal'), // ... ); return new Response(json_encode($events), 200, array( 'Content-Type' => 'application/json' )); });

Page 21: Silex meets SOAP & REST

$app->post('/event', function (Request $request) use ($app) { // Get POST data or 400 HTTP response if (!$data = $request->get('event')) { return new Response('Missing parameters.', 400); } // Persist data to the database $event = new Event() $event->title = $data['title']; $event->venue = $data['venue']; $event->save(); // Trigger redirect to the newly created record URI return $app->redirect('/event/'. $event->id, 201); });

Page 22: Silex meets SOAP & REST

$app->put('/event/{id}', function ($id) use ($app) { if (!$data = $request->get('event')) { return new Response('Missing parameters.', 400); } if (!$event = $app['event_manager']->find($id)) { return new Response('Event not found.', 404); } $event->title = $data['title']; $event->venue = $data['venue']; $event->save(); return new Response('Event updated.', 200); });

Page 23: Silex meets SOAP & REST

$app->delete('/event/{id}', function ($id) use ($app) { $event = $app['event_manager']->find($id); if (!$event) { return new Response('Event not found.', 404); } $event->delete(); return new Response('Event deleted.', 200); });

Page 24: Silex meets SOAP & REST

Advanced routing

Page 25: Silex meets SOAP & REST

$app->get('/archive/{year}/{month}', function ($month, $year) { // ... }) ->bind('archives') // Route name ->value('year', date('Y')) // Default parameter value ->value('month', date('m')) ->assert('year', '\d{4}') // Parameter format ->assert('month', '\d{2}');

Page 26: Silex meets SOAP & REST

Events management

Page 27: Silex meets SOAP & REST

$app->before(function (Request $request) use ($app) { $user = $request->server->get('PHP_AUTH_USER'); $pwd = $request->server->get('PHP_AUTH_PW'); if ( $app['api_user'] !== $user || $app['api_pwd'] !== $pwd ) { return new Response('Unauthorized', 403); } });

Page 28: Silex meets SOAP & REST

$app->after(function (Request $request, Response $response) {

// Get URI parameter to determine requested output format $format = $request->attributes->get('format');

switch ($format) { case 'xml': $response->headers->set('Content-Type', 'text/xml'); break; case 'json': $response->headers->set('Content-Type', 'text/json'); break; default: $response->headers->set('Content-Type', 'text/plain'); break; } });

Page 29: Silex meets SOAP & REST

Exception and error handling

Page 30: Silex meets SOAP & REST

$app->error(function (\Exception $e, $code) { switch ($code) { case 400: $message = 'Bad request.'; break; case 404: $message = 'Page not found.'; break; default: $message = 'Internal Server Error.'; } return new Response($message, $code); });

Page 31: Silex meets SOAP & REST

$app['debug'] = true;

Page 32: Silex meets SOAP & REST

$app->post('/event', function (Request $request) use ($app) {

if (!$event = $request->get('event')) {

$app->abort(400, 'Missing parameters.');

}

// ...

return $app->redirect('/event/'. $event->id, 201);

});

Page 33: Silex meets SOAP & REST

Logging with Monolog

Page 34: Silex meets SOAP & REST

use Silex\Provider\MonologServiceProvider;

$app->register(new MonologServiceProvider(), array(

'monolog.logfile' => __DIR__.'/../logs/app.log',

'monolog.class_path' => __DIR__.'/../vendor/monolog/src',

));

Page 35: Silex meets SOAP & REST

if ($app['debug']) {

$app['monolog']->addInfo('Testing the Monolog logging.');

$app['monolog']->addDebug('Method foo() was called.');

$app['monolog']->addWarning('Missing parameter "bar".');

$app['monolog']->addError('Class Foo does not exist.');

}

Page 36: Silex meets SOAP & REST

Database interactions with Doctrine

Page 37: Silex meets SOAP & REST

use Silex\Provider\DoctrineServiceProvider;

$app->register(new DoctrineServiceProvider(), array(

'db.options' => array(

'driver' => 'pdo_mysql',

'host' => 'localhost',

'user' => 'root',

'dbname' => 'event_demo',

),

'db.dbal.class_path' => __DIR__.'/../vendor/doctrine-dbal/lib',

'db.common.class_path' => __DIR__.'/../vendor/doctrine-common/lib',

));

Page 38: Silex meets SOAP & REST

$app->get('/events', function () use ($app) {

$query = 'SELECT id, title, venue FROM events';

$events = $app['db']->fetchAll($query);

return new Response(json_encode($events));

});

Page 39: Silex meets SOAP & REST

Input validation

Page 40: Silex meets SOAP & REST

use Silex\Provider\ValidatorServiceProvider;

$app->register(new ValidatorServiceProvider());

Page 41: Silex meets SOAP & REST

$app['validator']->validate($object);

Page 42: Silex meets SOAP & REST

namespace Confeet\Model;

use Symfony\Component\Validator\Mapping\ClassMetadata;

use Symfony\Component\Validator\Constraints\NotBlank;

use Symfony\Component\Validator\Constraints\MaxLength;

class Event extends Model

{

private $title;

private $venue;

static public function loadValidatorMetadata(ClassMetadata $metadata)

{

$metadata->addPropertyConstraint('title', new NotBlank());

$metadata->addPropertyConstraint('title', new MaxLength(array('limit' => 50)));

$metadata->addPropertyConstraint('venue', new NotBlank());

}

}

Page 43: Silex meets SOAP & REST

$app->post('/event', function (Request $request) use ($app) {

if (!$data = $request->get('event')) {

$app->abort(400, 'Missing parameters.');

}

$event = new Event()

$event->setTitle($data['title']);

$event->setVenue($data['venue']);

if (count($app['validator']->validate($event)) > 0) {

$app->abort(400, 'Invalid parameters.');

}

$event->save();

return $app->redirect('/event/'. $event->id, 201);

});

Page 44: Silex meets SOAP & REST

Template engine

Page 45: Silex meets SOAP & REST

§ Fast

§ Concise and rich syntax

§ Automatic output escaping

§ Modern features

§ Extensible

Twig

Page 46: Silex meets SOAP & REST

use Silex\Provider\TwigServiceProvider;

$app->register(new TwigServiceProvider(), array(

'twig.path' => __DIR__.'/../views',

'twig.class_path' => __DIR__.'/../vendor/twig/lib',

));

Page 47: Silex meets SOAP & REST

$app->get('/events.{format}', function ($format) use ($app) {

$events = array(

array('name' => 'OSIDays', 'venue' => 'Bangalore'),

array('name' => 'PHP Tour', 'venue' => 'Lille'),

array('name' => 'Confoo', 'venue' => 'Montreal'),

// ...

);

return $app['twig']->render('events.'.$format.'.twig', array(

'events' => $events,

));

})

->assert('format', 'xml|json');

Page 48: Silex meets SOAP & REST

<!-- views/events.xml.twig -->

<?xml version="1.0" encoding="utf-8" ?>

<events>

{% for event in events %}

<event>

<title>{{ event.title }}</title>

<venue>{{ event.venue }}</venue>

<begin>{{ event.startAt }}</begin>

<end>{{ event.endAt }}</end>

</event>

{% endfor %}

<events>

Page 49: Silex meets SOAP & REST

HTTP Caching & ESI

Page 50: Silex meets SOAP & REST

Reverse Proxy Caching

Page 51: Silex meets SOAP & REST

use Silex\Provider\HttpCacheServiceProvider;

$app->register(new HttpCacheServiceProvider(), array(

'http_cache.cache_dir' => __DIR__.'/../cache',

));

Page 52: Silex meets SOAP & REST

$app->get('/events', function () use ($app) {

$events = array(

array('name' => 'OSIDays', 'venue' => 'Bangalore'),

// ...

);

$content = $app['twig']->render('events.twig', array(

'events' => $events,

));

return new Response($content, 200, array(

'Cache-Control' => 'public, s-maxage=3600',

'Surrogate-Control' => 'content="ESI/1.0"',

));

});

Page 53: Silex meets SOAP & REST

Edge Side Includes

Page 54: Silex meets SOAP & REST

<!-- views/events.twig -->

<?xml version="1.0" encoding="utf-8" ?>

<events>

<esi:include src="/metadata" />

<!-- ... -->

<events>

Page 55: Silex meets SOAP & REST

$app->get('/metadata', function () use ($app) {

return new Response('<meta>...</meta>', 200, array(

'Cache-Control' => 'public, s-maxage=600',

));

});

Page 56: Silex meets SOAP & REST

Functional testing

Page 57: Silex meets SOAP & REST

Client Crawler PHPUnit

Page 58: Silex meets SOAP & REST

class EventApiTest extends Silex\WebTestCase

{

public function testRecentEvents()

{

$client = $this->createClient();

$crawler = $client->request('GET', '/events.xml');

$response = $client->getResponse();

$this->assertTrue($response->isOk());

$this->assertEquals(5, count($crawler->filter('event')));

$this->assertRegExp('/OSIDays/', $response->getContent());

...

}

}

Page 59: Silex meets SOAP & REST

Integrating Zend Soap

Page 60: Silex meets SOAP & REST

$path = __DIR__.'/../vendor/zend/library';

$app['autoloader']->registerNamespace('Zend', $path);

Page 61: Silex meets SOAP & REST

use Zend\Soap\Server;

use Confeet\Model\EventService;

$app->get('/gateway', function (Request $request) use ($app) {

$server = new Server();

$server->setObject(new EventService($app));

$server->setReturnResponse(true);

return new Response($server->handle(), 200, array(

'Content-Type' => 'text/xml'

));

});

Page 62: Silex meets SOAP & REST

92-98, boulevard Victor Hugo

92 115 Clichy Cedex, France

[email protected] (+33 (0)140 998 211)

sensiolabs.com - symfony.com – trainings.sensiolabs.com

Ques%ons?