Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced Silex (sflive2012)

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Advanced Silex (sflive2012)

Avatar for Igor Wiedler

Igor Wiedler

June 08, 2012
Tweet

More Decks by Igor Wiedler

Other Decks in Programming

Transcript

  1. $app->get('/', function () use ($app) { $id = $app['request']->get('parent'); $parentPaste

    = null; if ($id) { $parentPaste = $app['predis']->hgetall($id); } return $app['twig']->render('index.html', array( 'paste' => $parentPaste, )); }) ->bind('homepage');
  2. $app->post('/', function (Request $request) use ($app) { $content = $request->get('content',

    ''); $id = substr(hash('sha512', mt_rand()), 0, 8); $paste = array( 'content' => $content, 'language' => $request->get('language', ''), 'created_at' => time(), ); if ('' === trim($paste['content'])) { $page = $app['twig']->render('index.html', array( 'errors' => array('you must enter some content'), 'paste' => $paste, )); return new Response($page, 400); } $app['predis']->hmset($id, $paste); return $app->redirect($app['url_generator']->generate('view', array( 'id' => $id, ))); }) ->bind('create');
  3. $app->get('/{id}', function ($id) use ($app) { $paste = $app['predis']->hgetall($id); if

    (!$paste) { return $app->abort(404, 'paste not found'); } $copy_url = $app['url_generator']->generate('homepage', array( 'parent' => $id, )); return $app['twig']->render('view.html', array( 'copy_url' => $copy_url, 'paste' => $paste, )); }) ->bind('view') ->assert('id', '[0-9a-f]{8}');
  4. <?php require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use

    Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; $app = new Silex\Application(); $app->register(new Silex\Provider\TwigServiceProvider(), array( 'twig.path' => __DIR__.'/../views', 'twig.options' => array('cache' => __DIR__.'/../cache/twig'), )); $app->register(new Silex\Provider\UrlGeneratorServiceProvider()); $app->register(new Predis\Silex\PredisServiceProvider()); $app['app.languages'] = $app->share(function () { $languages = array(); $finder = new Finder(); foreach ($finder->name('*.min.js')->in(__DIR__.'/../web/shjs/lang') as $file) { if (preg_match('#sh_(.+).min.js#', basename($file), $matches)) { $languages[] = $matches[1]; } } return $languages; }); $app->before(function () use ($app) { // set up some template globals $app['twig']->addGlobal('base_path', $app['request']->getBasePath()); $app['twig']->addGlobal('index_url', $app['url_generator']->generate('homepage')); $app['twig']->addGlobal('create_url', $app['url_generator']->generate('create')); $app['twig']->addGlobal('languages', $app['app.languages']); }); $app->get('/', function () use ($app) { $parentPasteId = $app['request']->get('parent'); $parentPaste = null; if ($parentPasteId) { $parentPaste = $app['predis']->hgetall($id); } return $app['twig']->render('index.html', array( 'paste' => $parentPaste, )); }) ->bind('homepage'); $app->get('/create', function () use ($app) { return $app->redirect($app['url_generator']->generate('homepage')); }); $app->post('/create', function (Request $request) use ($app) { $content = $request->get('content', ''); $content = $this->normalizeContent($content); $id = $this->generateId($content); $paste = array( 'content' => $content, ); $language = $request->get('language', ''); if (in_array($language, $this->languages)) { $paste['language'] = $language; } $paste['created_at'] = time(); $errors = array(); if ('' === trim($paste['content'])) { $errors[] = 'you must enter some content'; } if ($errors) { $page = $app['twig']->render('index.html', array( 'errors' => $errors, 'paste' => $paste, )); return new Response($page, 400); } $app['predis']->hmset($id, $paste) return $app->redirect($app['url_generator']->generate('view', array('id' => $id))); }) ->bind('create'); $app->get('/about', function () use ($app) { return $app['twig']->render('about.html'); }); $app->get('/{id}', function ($id) use ($app) { $paste = $app['predis']->hgetall($id); if (!$paste) { throw new NotFoundHttpException('paste not found'); } return $app['twig']->render('view.html', array( 'copy_url' => $app['url_generator']->generate('homepage', array(' 'paste' => $paste, )); }) ->bind('view') ->assert('id', '[0-9a-f]{8}'); $app->error(function (Exception $e) use ($app) { $code = ($e instanceof HttpException) ? $e->getStatusCode() : 500; return new Response($app['twig']->render('error.html', array( 'message' => $e->getMessage(), )), $code); }); $app->run();
  5. ᵓᴷᴷ composer.json ᵓᴷᴷ README.md ᵓᴷᴷ phpunit.xml.dist ᵓᴷᴷ src ᴹ ᵋᴷᴷ

    app.php ᵓᴷᴷ tests ᵓᴷᴷ views ᵋᴷᴷ web ᵋᴷᴷ index.php
  6. ᵓᴷᴷ composer.json ᵓᴷᴷ README.md ᵓᴷᴷ phpunit.xml.dist ᵓᴷᴷ src ᴹ ᵋᴷᴷ

    app.php ᵓᴷᴷ tests ᵓᴷᴷ views ᵋᴷᴷ web ᵋᴷᴷ index.php
  7. ᵓᴷᴷ composer.json ᵓᴷᴷ README.md ᵓᴷᴷ phpunit.xml.dist ᵓᴷᴷ src ᴹ ᵋᴷᴷ

    app.php ᵓᴷᴷ tests ᵓᴷᴷ views ᵋᴷᴷ web ᵋᴷᴷ index.php
  8. $app->post('/', function (Request $request) use ($app) { $content = $request->get('content',

    ''); $id = substr(hash('sha512', mt_rand()), 0, 8); $paste = array( 'content' => $content, 'language' => $request->get('language', ''), 'created_at' => time(), ); if ('' === trim($paste['content'])) { $page = $app['twig']->render('index.html', array( 'errors' => array('you must enter some content'), 'paste' => $paste, )); return new Response($page, 400); } $app['predis']->hmset($id, $paste); return $app->redirect($app['url_generator']->generate('view', array( 'id' => $id, ))); }) ->bind('create');
  9. $app->post('/', function (Request $request) use ($app) { $content = $request->get('content',

    ''); $id = substr(hash('sha512', mt_rand()), 0, 8); $paste = array( 'content' => $content, 'language' => $request->get('language', ''), 'created_at' => time(), ); if ('' === trim($paste['content'])) { $page = $app['twig']->render('index.html', array( 'errors' => array('you must enter some content'), 'paste' => $paste, )); return new Response($page, 400); } $app['predis']->hmset($id, $paste); return $app->redirect($app['url_generator']->generate('view', array( 'id' => $id, ))); }) ->bind('create'); Parsing
  10. $app->post('/', function (Request $request) use ($app) { $content = $request->get('content',

    ''); $id = substr(hash('sha512', mt_rand()), 0, 8); $paste = array( 'content' => $content, 'language' => $request->get('language', ''), 'created_at' => time(), ); if ('' === trim($paste['content'])) { $page = $app['twig']->render('index.html', array( 'errors' => array('you must enter some content'), 'paste' => $paste, )); return new Response($page, 400); } $app['predis']->hmset($id, $paste); return $app->redirect($app['url_generator']->generate('view', array( 'id' => $id, ))); }) ->bind('create'); Validation
  11. $app->post('/', function (Request $request) use ($app) { $content = $request->get('content',

    ''); $id = substr(hash('sha512', mt_rand()), 0, 8); $paste = array( 'content' => $content, 'language' => $request->get('language', ''), 'created_at' => time(), ); if ('' === trim($paste['content'])) { $page = $app['twig']->render('index.html', array( 'errors' => array('you must enter some content'), 'paste' => $paste, )); return new Response($page, 400); } $app['predis']->hmset($id, $paste); return $app->redirect($app['url_generator']->generate('view', array( 'id' => $id, ))); }) ->bind('create'); Storage
  12. SRP

  13. public function createPasteFromRequest(Request $request) { $content = $request->get('content', ''); $content

    = $this->normalizeContent($content); $id = $this->generateId(); $paste = array( 'content' => $content, ); $language = $request->get('language', ''); if (in_array($language, $this->languages)) { $paste['language'] = $language; } $paste['created_at'] = time(); return array($id, $paste); }
  14. public function createPasteFromRequest(Request $request) { $content = $request->get('content', ''); $content

    = $this->normalizeContent($content); $id = $this->generateId(); $paste = array( 'content' => $content, ); $language = $request->get('language', ''); if (in_array($language, $this->languages)) { $paste['language'] = $language; } $paste['created_at'] = time(); return array($id, $paste); } Bad
  15. public function createPasteFromRequest(Request $request) { $content = $request->get('content', ''); $content

    = $this->normalizeContent($content); $id = $this->generateId(); $paste = array( 'content' => $content, ); $language = $request->get('language', ''); if (in_array($language, $this->languages)) { $paste['language'] = $language; } $paste['created_at'] = $request->server->get('REQUEST_TIME'); return array($id, $paste); }
  16. private $languages; public function __construct(array $languages) { $this->languages = $languages;

    } public function normalizeContent($content) { return preg_replace(array('#\r?\n#', '#\r#'), "\n", $content); } public function generateId() { return substr(hash('sha512', mt_rand()), 0, 8); }
  17. namespace Igorw\Trashbin; class Validator { public function validate(array $paste) {

    $errors = array(); if ('' === trim($paste['content'])) { $errors[] = 'you must enter some content'; } return $errors; } }
  18. namespace Igorw\Trashbin; use Predis\Client; class Storage { private $redis; public

    function __construct(Client $redis) { $this->redis = $redis; } public function get($id) { return $this->redis->hgetall($id); } public function set($id, array $data) { return $this->redis->hmset($id, $data); } }
  19. $app['app.parser'] = $app->share(function () use ($app) { return new Parser($app['app.languages']);

    }); $app['app.validator'] = $app->share(function () { return new Validator(); }); $app['app.storage'] = $app->share(function () use ($app) { return new Storage($app['predis']); });
  20. $app->post('/', function () use ($app) { list($id, $paste) = $app['app.parser']

    ->createPasteFromRequest($app['request']); $errors = $app['app.validator']->validate($paste); if ($errors) { $page = $app['twig']->render('index.html', array( 'errors' => $errors, 'paste' => $paste, )); return new Response($page, 400); } $app['app.storage']->set($id, $paste); $url = $app['url_generator']->generate('view', array('id' => $id)); return $app->redirect($url); }) ->bind('create');
  21. use Igorw\Trashbin\Validator; class ValidatorTest extends PHPUnit_Framework_TestCase { /** * @dataProvider

    provideValidate */ public function testValidate($expected, $input) { $validator = new Validator(); $this->assertEquals($expected, $validator->validate($input)); } public function provideValidate() { return array( array(array('you must enter some content'), array('content' => '')), array(array('you must enter some content'), array('content' => ' ')), array(array('you must enter some content'), array('content' => "\t")), array(array('you must enter some content'), array('content' => "\t \n")), array(array(), array('content' => 'hello')), array(array(), array('content' => 'foobar! ')), array(array(), array('content' => 'äöü~')), ); } }
  22. { "require": { "silex/silex": ">=1.0.0-dev", "symfony/finder": ">=2.1-dev", "twig/twig": "1.6.0", "predis/service-provider":

    "dev-master" }, "require-dev": { "symfony/browser-kit": "2.1.*@dev", "symfony/css-selector": "2.1.*@dev", "symfony/dom-crawler": "2.1.*@dev" }, "autoload": { "psr-0": { "Igorw\\Trashbin": "src" } } }
  23. use Silex\WebTestCase; class FunctionalTest extends WebTestCase { public function createApplication()

    { $app = require __DIR__.'/../src/app.php'; $app['app.storage'] = $this ->getMockBuilder('Igorw\Trashbin\Storage') ->disableOriginalConstructor() ->getMock(); unset($this->app['exception_handler']); return $app; } // ... }
  24. $paste = array('content' => 'foobar', 'created_at' => 1337882841); $this->app['app.storage'] ->expects($this->once())

    ->method('set') ->with($this->isType('string'), $paste); $this->app['app.storage'] ->expects($this->once()) ->method('get') ->with($this->isType('string')) ->will($this->returnValue($paste));
  25. $client = $this->createClient(); $client->setServerParameters(array('REQUEST_TIME' => 1337882841)); $crawler = $client->request('GET', '/');

    $form = $crawler->filter('form')->form(); $form['content'] = 'foobar'; $crawler = $client->submit($form); $crawler = $client->followRedirect(); $response = $client->getResponse(); $this->assertTrue($response->isOk()); $this->assertContains('foobar', $response->getContent());
  26. public function testCreatePasteWithoutContentShouldFail() { $client = $this->createClient(); $crawler = $client->request('GET',

    '/'); $form = $crawler->filter('form')->form(); $form['content'] = ''; $client->submit($form); $response = $client->getResponse(); $this->assertSame(400, $response->getStatusCode()); }
  27. public function testViewPaste() { $paste = array('content' => 'foobar', 'created_at'

    => 1337882841); $this->app['app.storage'] ->expects($this->once()) ->method('get') ->with('abcdef12') ->will($this->returnValue($paste)); $client = $this->createClient(); $client->request('GET', '/abcdef12'); $response = $client->getResponse(); $this->assertTrue($response->isOk()); $this->assertContains('foobar', $response->getContent()); }
  28. public function testViewPasteWithInvalidId() { $this->app['app.storage'] ->expects($this->once()) ->method('get') ->with('00000000') ->will($this->returnValue(null)); $client

    = $this->createClient(); $client->request('GET', '/00000000'); $response = $client->getResponse(); $this->assertSame(404, $response->getStatusCode()); $this->assertContains('paste not found', $response->getContent()); }
  29. Ω