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

Symfony without the framework

Symfony without the framework

Many of us do not know the world outside frameworks - you simply have to use one to be regarded a well-rounded engineer. Symfony is one of them, a quite interesting choice if you look for very popular, actively maintained and well-engineered foundation for years to come. But there is one advantage that not many of us are aware of - the level of reusability of its components. I want to show how to build fully-featured solutions using Symfony components without relying on virtually anything that would make you think that it is a stock framework. I will show how I maintained several projects in various companies I worked for.

Tomasz Kowalczyk

June 30, 2018
Tweet

More Decks by Tomasz Kowalczyk

Other Decks in Programming

Transcript

  1. <?php declare(strict_types=1); namespace X; use Symfony\Component\HttpFoundation\Request; use Thunder\Example\Application\Example\Kernel\ExampleKernel; require __DIR__.'/../../../vendor/autoload.php';

    require __DIR__.'/../../../var/cache/ExampleContainer.php'; $request = Request::createFromGlobals(); $kernel = new ExampleKernel(new \ExampleContainer()); $response = $kernel->handle($request); $response->send();
  2. services: _defaults: autowire: false autoconfigure: false public: false index.controller: class:

    Thunder\Example\Application\Example\Controller\IndexController arguments: ['@routing'] public: true routing: class: Symfony\Component\Routing\Router arguments: ['@routing.loader', 'routing.yaml', { cache_dir: '../var/cache' }] public: true routing.loader: class: Symfony\Component\Routing\Loader\YamlFileLoader arguments: ['@routing.locator'] routing.locator: class: Symfony\Component\Config\FileLocator arguments: [['../etc']]
  3. <?php declare(strict_types=1); namespace Thunder\Example\Application\Console\Command; use …; final class ContainerGenerateCommand extends

    Command { protected function configure(): void { /* … */ } protected function execute(InputInterface $input, OutputInterface $output): void { $yaml = $input->getOption('services'); $target = $input->getOption('target'); $builder = new ContainerBuilder(); $loader = new YamlFileLoader($builder, new FileLocator(\dirname($yaml))); $loader->load(basename($yaml)); $builder->compile(); $dumper = new PhpDumper($builder); file_put_contents($target, $dumper->dump([ 'class' => str_replace('.php', '', basename($target)), ])); } }
  4. <?php declare(strict_types=1); namespace Thunder\Example\Application\Console\Command; use …; final class RoutingGenerateCommand extends

    Command { protected function configure(): void { /* … */ } protected function execute(InputInterface $input, OutputInterface $output): void { $resource = $input->getOption('resource'); $target = $input->getOption('target'); $loader = new YamlFileLoader(new FileLocator([\dirname($resource)])); $routing = new Router($loader, basename($resource), [ 'cache_dir' => $target, ]); $routing->match('/'); } }
  5. class ExampleContainer extends Container { public function __construct() { $this->services

    = $this->privates = array(); $this->methodMap = array( 'index.controller' => 'getIndex_ControllerService', 'routing' => 'getRoutingService', ); $this->aliases = array(); } protected function getIndex_ControllerService() { return $this->services['index.controller'] = new \Thunder\Example\Application\Example\Controller\IndexController( ($this->services['routing'] ?? $this->getRoutingService() ); } protected function getRoutingService() { return $this->services['routing'] = new \Symfony\Component\Routing\Router( new \Symfony\Component\Routing\Loader\YamlFileLoader( new \Symfony\Component\Config\FileLocator(array(0 => '../etc')) ), 'routing.yaml', array('cache_dir' => '../var/cache') ); } }
  6. final class ExampleKernel implements HttpKernelInterface { private $container; public function

    __construct(\AppContainer $container) { $this->container = $container; } public function handle(Request $request, $type, $catch) { // to be continued... } }
  7. $routing = $this->container->get('routing'); $context = new RequestContext(); $context->fromRequest($request); $routing->setContext($context); $route

    = $routing->matchRequest($request); $request->attributes->replace($route); [$ctrl, $action] = $route['controller']; return $container->get($ctrl)->{$action}($request);
  8. services: user.controller: class: Application\Web\Controller\UserController arguments: ['@user.repository'] user.repository: alias: user.repository.doctrine user.repository.doctrine:

    class: Application\Web\Repository\DoctrineUserRepository arguments: ['@doctrine.em'] user.repository.memory: class: Application\Web\Repository\MemoryUserRepository
  9. index.index: path: / methods: [GET] defaults: controller: [index.controller, indexAction] _controller:

    index.controller:indexAction user.view: path: /user/{id} methods: [GET] defaults: controller: [user.controller, viewAction] _controller: user.controller:viewAction requirements: id: \d+
  10. final class UserController { private $users; public function __construct(UserRepositoryInterface $users)

    { $this->users = $users; } public function viewAction(Request $request) { // to be continued... } }
  11. $id = $request->attributes->get('id'); try { $user = $this->users->findById(new UserId($id)); }

    catch(UserNotFoundException $e) { return new JsonResponse(null, Response::HTTP_NOT_FOUND); } return new JsonResponse($user->toArray(), Response::HTTP_OK);
  12. final class MemoryUserRepository implements UserRepositoryInterface { private $users = [];

    public function findById(UserId $id): User { if(false === array_key_exists($id->toString(), $this->users)) { throw new UserNotFoundException(); } return $this->users[$id->toString()]; } public function add(User $user): void { if(array_key_exists($id->toString(), $this->users)) { throw new DuplicateUserException(); } $this->users[$user->getId()->toString()] = $user; } }
  13. final class DoctrineOrmUserRepository implements UserRepositoryInterface { private $em; public function

    __construct(EntityManagerInterface $em) { $this->em = $em; } public function findById(UserId $id): User { return $this->em->find(User::class, $id->toString()); } public function add(User $user): void { $this->em->persist($user); } }
  14. Application ﹂src ﹂User ﹂Application ﹂Command ﹂CreateUserCommand.php ﹂Domain ﹂User.php ﹂UserId.php ﹂UserRepositoryInterface.php

    ﹂Infrastructure ﹂Persistence ﹂MemoryUserRepository.php ﹂DoctrineOrmUserRepository.php ﹂tests ﹂User ﹂Domain ﹂UserTest.php
  15. { // ... "autoload": { "psr-4": { "Thunder\\Agnostic\\": "src", "Thunder\\Agnostic\\Tests\\":

    "tests", "Thunder\\AgnosticBundle\\": "bundle" } } // ... }