$30 off During Our Annual Pro Sale. View Details »

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. symfony

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. ➔ ➔

    View Slide

  7. ➔ ➔ ➔

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. 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();

    View Slide

  32. View Slide

  33. 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']]

    View Slide

  34. 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)),
    ]));
    }
    }

    View Slide

  35. 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('/');
    }
    }

    View Slide

  36. apps/console/bin/example container:generate
    --services=apps/example/etc/services.yaml
    --target=apps/example/var/cache/ExampleContainer.php
    apps/console/bin/example routing:generate
    --resource=apps/example/etc/routing.yaml
    --target=apps/example/var/cache

    View Slide

  37. 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')
    );
    }
    }

    View Slide

  38. View Slide

  39. 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...
    }
    }

    View Slide

  40. $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);

    View Slide

  41. View Slide

  42. 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

    View Slide

  43. 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+

    View Slide

  44. final class UserController
    {
    private $users;
    public function __construct(UserRepositoryInterface $users)
    {
    $this->users = $users;
    }
    public function viewAction(Request $request)
    {
    // to be continued...
    }
    }

    View Slide

  45. $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);

    View Slide

  46. View Slide

  47. interface UserRepositoryInterface
    {
    public function findById(UserId $id): User;
    public function add(User $user): void
    }

    View Slide

  48. 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;
    }
    }

    View Slide

  49. 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);
    }
    }

    View Slide

  50. View Slide

  51. 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

    View Slide

  52. Application
    ﹂apps
    ﹂example
    ﹂etc
    ﹂routing.yaml
    ﹂services.yaml
    ﹂src
    ﹂Controller
    ﹂UserController.php
    ﹂Service
    ﹂TokenService.php
    ﹂Kernel
    ﹂ExampleKernel.php
    ﹂web
    ﹂app.php

    View Slide

  53. Application
    ﹂apps
    ﹂console
    ﹂bin
    ﹂example
    ﹂src
    ﹂Command
    ﹂ContainerGenerateCommand.php
    ﹂RoutingGenerateCommand.php

    View Slide

  54. Application
    ﹂apps
    ﹂example
    ﹂features
    ﹂user-auth.feature
    ﹂tests
    ﹂UserTest.php
    ﹂var
    ﹂cache
    ﹂router.php
    ﹂container.php
    ﹂logs
    ﹂error.log

    View Slide

  55. View Slide

  56. View Slide

  57. Library
    ﹂src
    ﹂Provider
    ﹂ProviderInterface.php
    ﹂AgnosticProvider.php
    ﹂Agnostic.php
    ﹂tests
    ﹂AgnosticTest.php

    View Slide

  58. View Slide

  59. Library
    ﹂bundle
    ﹂etc
    ﹂services.yaml
    ﹂src
    ﹂DependencyInjection
    ﹂Configuration.php
    ﹂AgnosticExtension.php
    ﹂AgnosticBundle.php
    ﹂tests
    ﹂AgnosticBundleTest.php

    View Slide

  60. {
    // ...
    "autoload": {
    "psr-4": {
    "Thunder\\Agnostic\\": "src",
    "Thunder\\Agnostic\\Tests\\": "tests",
    "Thunder\\AgnosticBundle\\": "bundle"
    }
    }
    // ...
    }

    View Slide

  61. View Slide

  62. View Slide

  63. View Slide

  64. View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. View Slide

  69. View Slide

  70. View Slide

  71. Resources
    Images

    View Slide

  72. View Slide