Build your framework like constructicons (ZendCon 2016)

Build your framework like constructicons (ZendCon 2016)

While we have a strong offering of full-stack frameworks and microframeworks, the rise of components and libraries combined with Composer allows us to easily build our own framework without reinventing the wheel. In this talk, you'll learn how the total can be more than the sum of the parts, just like how Devastator was stronger than the individual Constructicons in Transformers.

A8f72e32766355f12a56ede9aaa0ee78?s=128

Stefan Koopmanschap

October 19, 2016
Tweet

Transcript

  1. None
  2. Build your framework like constructicons

  3. Hi, I'm Stefan → Ingewikkeld → former Zend Framework translator

    → former Symfony Community Manager
  4. None
  5. None
  6. None
  7. None
  8. None
  9. Wait, what?

  10. Who has built their own framework?

  11. None
  12. Who is still using a custom-built framework?

  13. None
  14. The case for a framework

  15. None
  16. None
  17. Open Source

  18. So why would we build it ourselves? → Custom needs

    → Because we're developers (NIH) → An open source framework is overkill → As a learning experience
  19. Surely, there are many more reasons

  20. It is OK to build your own framework

  21. It is not OK to write all the code for

    it yourself
  22. Well, actually...

  23. None
  24. WRONG MOVIE

  25. Composer

  26. None
  27. None
  28. Request and response

  29. Routing

  30. Templating

  31. Handling configuration

  32. Sending e-mail

  33. Doing validation

  34. The Code

  35. composer init

  36. composer init { "name": "stefankoopmanschap/constructicons", "authors": [ { "name": "Stefan

    Koopmanschap", "email": "stefan@ingewikkeld.net" } ], "require": {} }
  37. Request and response → react/http → illuminate/http → symfony/http-foundation

  38. Request and response composer require symfony/http-foundation

  39. Request and response "require": { "symfony/http-foundation": "^3.1" }

  40. index.php and composer require( __DIR__ . ' /../vendor/autoload.php' );

  41. Request and response use \Symfony\Component\HttpFoundation\Request; use \Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals();

    $response = new Response( 'Hello ' . $request->get('name', 'world') ); $response->send();
  42. /?name=Stefan results in Hello Stefan

  43. None
  44. Routing → symfony/routing → illuminate/routing → zendframework/zend-router → nikic/fast-route

  45. Routing composer require nikic/fast-route

  46. Routing $dispatcher = FastRoute\simpleDispatcher( function(FastRoute\RouteCollector $r) use ($request) { $r->addRoute('GET',

    '/hello', function() use ($request) { $response = new Response( 'Hello ' . $request->get('name', 'world') ); $response->send(); }); $r->addRoute('GET', '/goodbye', function() use ($request) { $response = new Response( 'Goodbye ' . $request->get('name', 'world') ); $response->send(); }); } );
  47. Routing $dispatcher = FastRoute\simpleDispatcher( function(FastRoute\RouteCollector $r) use ($request) { //

    do stuff here } );
  48. Routing $r->addRoute('GET', '/hello', function() use ($request) { $response = new

    Response( 'Hello ' . $request->get('name', 'world') ); $response->send(); }); $r->addRoute('GET', '/goodbye', function() use ($request) { $response = new Response( 'Goodbye ' . $request->get('name', 'world') ); $response->send(); });
  49. Routing $routeInfo = $dispatcher->dispatch( $request->getMethod(), $request->getRequestUri() ); if (is_callable($routeInfo[1])) {

    $routeInfo[1](); }
  50. Routing $r->addRoute('GET', '/hello[/{name}]', function($params) use ($request) { $name = $params['name']

    ?? 'world'; $response = new Response( 'Hello ' . $name ); $response->send(); }); $r->addRoute('GET', '/goodbye[/{name}]', function() use ($request) { $name = $params['name'] ?? 'world'; $response = new Response( 'Goodbye ' . $name ); $response->send(); });
  51. Routing if (is_callable($routeInfo[1])) { $params = ($routeInfo[2]) ?? []; $routeInfo[1]($params);

    }
  52. None
  53. Templating → zetacomponents/template → eden/template → twig/twig

  54. Templating composer require twig/twig

  55. Templating $twigLoader = new Twig_Loader_Filesystem( __DIR__ . '/../views/' ); $twig

    = new Twig_Environment($twigLoader, [ 'cache' => '/tmp/twigCache', ]);
  56. Templating function(FastRoute\RouteCollector $r) use ( $request, $twig ) {

  57. Templating $r->addRoute( 'GET', '/hello[/{name}]', function($params) use ($request, $twig) { $name

    = $params['name'] ?? 'world'; $response = new Response( $twig->render('hello.twig', [ 'name' => $name ] ) ); $response->send(); });
  58. Templating hello.twig Hello {{ name }} goodbye.twig Goodbye {{ name

    }}
  59. Handling configuration → symfony/config → illuminate/config → packaged/config → werx/config

  60. Handling configuration $r->addRoute( 'GET', '/contact', function() use ($request, $twig) {

    $response = new Response( $twig->render('contact.twig', []) ); $response->send(); } );
  61. Handling configuration <form method="post" action="/contact"> Your name: <input type="name" /><br

    /> Your e-mailaddress: <input type="email" /><br /> Your message:<br /> <textarea></textarea><br /> <input type="submit" name="submit" value="Contact Us" /> </form>
  62. Handling configuration $r->addRoute( 'POST', '/contact', function() use ($request, $twig) {

    $mailbody = 'An e-mail was sent through the site with the following message: ' . $request->get('message') . "\n"; $mailbody .= 'Sent by: ' . $request->get('name') . '<' . $request->get('email') . '>'; mail( 'my-email@example.org', 'An e-mail from the site', $mailbody ); $response = new \Symfony\Component\HttpFoundation\RedirectResponse('/hello'); $response->send(); } );
  63. Handling configuration composer require werx/config

  64. Handling configuration $configProvider = new \werx\Config\Providers\JsonProvider( __DIR__ . '/../config/' );

    $config = new \werx\Config\Container($configProvider); $config->load('config');
  65. Handling configuration $r->addRoute( 'POST', '/contact', function() use ($request, $config) {

  66. Handling configuration mail( $config->get('contact_email'), 'An e-mail from the site', $mailbody

    );
  67. Sending e-mail → nette/mail → zetacomponents/mail → swiftmailer/swiftmailer

  68. Sending e-mail composer require swiftmailer/swiftmailer

  69. Sending e-mail $mailerTransport = Swift_SmtpTransport::newInstance( $config->get('mailer_server'), $config->get('mailer_port') )->setUsername($config->get('mailer_username')) ->setPassword($config->get('mailer_password')) ;

    $mailer = Swift_Mailer::newInstance($mailerTransport);
  70. Sending e-mail $message = Swift_Message::newInstance('An e-mail from the site') ->setFrom([

    $request->get('email'), $request->get('name')] ) ->setTo([ $config->get('contact_email') ]) ->setBody('An e-mail was sent through the site with the following message: ' . $request->get('message')) ; $mailer->send($message);
  71. Doing validation → symfony/validator → zendframework/zend-validator → particle/validator

  72. Doing validation composer require particle/validator

  73. Doing validation $contactMailValidator = new \Particle\Validator\Validator(); $contactMailValidator ->required('name') ->string(); $contactMailValidator

    ->required('email') ->email(); $contactMailValidator ->required('message') ->string();
  74. Doing validation $r->addRoute( 'POST', '/contact', function() use ($request, $config, $mailer,

    $contactMailValidator, $twig) { $result = $contactMailValidator->validate([ 'name' => $request->get('name'), 'email' => $request->get('email'), 'message' => $request->get('message') ]); if ($result->isValid()) { // send e-mail and redirect } return new Response($twig->render('contact.twig', [ 'errors' => $result->getMessages(), ])); });
  75. Summary → valid use cases for building your own framework

    → Leverage the power of open source components and composer → Only need a few lines of glue
  76. Some other components you could think of → DI (bitexpert/disco,

    symfony/dependency- injection, league/container) → Console (symfony/console, illuminate/console, webmozart/console) → http requests (Guzzle) → logging (monolog/monolog)
  77. None
  78. Photo credits → Foundation by Paul Sableman, cc-by 2.0 →

    Constructicon icons: James Peng
  79. Thank you! Questions? http://leftontheweb.com @skoop https://legacy.joind.in/19458