Symfony: Your Next Microframework (SymfonyCon 2015)

Post on 12-Apr-2017

5.812 views 1 download

Transcript of Symfony: Your Next Microframework (SymfonyCon 2015)

Symfony: Your Next Microframework

by your friend:

Ryan Weaver @weaverryan

by your friend:

Ryan Weaver @weaverryan

KnpUniversity.comgithub.com/weaverryan

Who is this guy?

> Lead for the Symfony documentation

> KnpLabs US - Symfony Consulting, training & general Kumbaya

> Writer for KnpUniversity.com Tutorials

> Husband of the much more talented @leannapelham

Thinking about 2 Problems

@weaverryan

Problem 1:Symfony Sucks

@weaverryan

@weaverryan

@weaverryan

@weaverryan

@weaverryan

Symfonyis too hard

@weaverryan

The Symfony Frameworkis too hard

@weaverryan

The components are not usually the problem

Why?

@weaverryan

Route

Controller

Response@weaverryan

WTF?

Useful Objects

1) Common tasks requiretoo much code

@weaverryan

2) Symfony is too big

@weaverryan

Too many files==

A Perceived Complexity

@weaverryan

@weaverryan

~ 25 files ~ 10 directories for Hello World

Problem 2:

Is my project macro or micro?

@weaverryan

Macro => Use Symfony

@weaverryan

Micro => Use Silex

@weaverryan

The fact we have this option is incredible

but…

@weaverryan

Silex has a slightly different tech stack

@weaverryan

Silex doesn’t have bundles

@weaverryan

Silex can’t evolve to a full stack Symfony App

@weaverryan

What if we just made Symfony smaller?

@weaverryan

6 files 62 lines of code

<?phpuse Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Config\Loader\LoaderInterface; class AppKernel extends Kernel{ public function registerBundles() { return array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), ); } public function registerContainerConfiguration($loader) { $loader->load( __DIR__.'/config/config_'.$this->getEnvironment().'.yml' ); }}

How small can we go?

@weaverryan

What is a Symfony Application?

@weaverryan

What is a Symfony App?

@weaverryan

1.A set of bundles 2.A container of services 3.Routes

Let’s create a newSymfony project

from nothing

@weaverryan

{ "require": { "symfony/symfony": "^2.8" }}

@weaverryan

composer.json

<?phpuse Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\HttpKernel\Kernel; require __DIR__.'/vendor/autoload.php'; class AppKernel extends Kernel{ public function registerBundles() { } public function registerContainerConfiguration($loader) { }}

index.php

<?phpuse Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Routing\RouteCollectionBuilder; // ...class AppKernel extends Kernel{ use MicroKernelTrait; public function registerBundles() { } protected function configureRoutes(RouteCollectionBuilder $routes) { } protected function configureContainer(ContainerBuilder $c, $loader) { }}

index.php

1) A set of bundles

2) Routes

3) A container of services

public function registerBundles(){ return array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle() );}

AppKernel

protected function configureContainer(ContainerBuilder $c, $loader) { $c->loadFromExtension('framework', array( 'secret' => 'S0ME_SECRET', ));}

AppKernel

// config.yml framework: secret: S0ME_SECRET

protected function configureRoutes(RouteCollectionBuilder $routes) { $routes->add('/random/{limit}', 'kernel:randomAction');}

AppKernelNew in 2.8!

service:methodName(Symfony’s controller as a service syntax)

public function randomAction($limit) { return new JsonResponse(array( 'number' => rand(0, $limit) ));}

AppKernel

<?php// ...require __DIR__.'/vendor/autoload.php'; class AppKernel extends Kernel{ // ...} $kernel = new AppKernel('dev', true); $request = Request::createFromGlobals();$response = $kernel->handle($request);$response->send();$kernel->terminate($request, $response);

index.php

How many files?

@weaverryan

How many lines of code?

2 files 52 lines of code

This is a full stack framework

@weaverryan

@weaverryan

1. Service Container 2. Routing 3. Events 4. ESI & Sub-Requests 5. Compatible with 3rd party bundles

Fast as Hell

@weaverryan

The goal is not to create single-file apps

@weaverryan

Clarity & Control

@weaverryan

Building a Realistic App

@weaverryan

github.com/weaverryan/docs-micro_kernel

Requirements:

@weaverryan

1. Add some organization 2. Load annotation routes 3. Web Debug Toolbar + Profiler 4. Twig

Reorganizeclass AppKernel extends Kernel{ }

// web/index.php$kernel = new AppKernel('dev', true);$request = Request::createFromGlobals();$response = $kernel->handle($request);$response->send();

public function registerBundles(){ $bundles = array( new FrameworkBundle(), new TwigBundle(), new SensioFrameworkExtraBundle() ); if ($this->getEnvironment() == 'dev') { $bundles[] = new WebProfilerBundle(); } return $bundles; }

app/AppKernel.php

protected function configureContainer(ContainerBuilder $c, $loader) { $loader->load(__DIR__.'/config/config.yml'); if (isset($this->bundles['WebProfilerBundle'])) { $c->loadFromExtension('web_profiler', array( 'toolbar' => true, 'intercept_redirects' => false, )); }}

app/AppKernel.php

@weaverryan

app/config/config.yml

framework: secret: S0ME_SECRET templating: engines: ['twig'] profiler: { only_exceptions: false }

@weaverryan

protected function configureContainer(ContainerBuilder $c, $loader) { $loader->load(__DIR__.'/config/config.yml'); if (isset($this->bundles['WebProfilerBundle'])) { $c->loadFromExtension('web_profiler', array( 'toolbar' => true, 'intercept_redirects' => false, )); }}

app/AppKernel.php

@weaverryan

Goodbye config_dev.yml

protected function configureRoutes(RouteCollectionBuilder $routes) { if (isset($this->bundles['WebProfilerBundle'])) { $routes->import( '@WebProfilerBundle/Resources/config/routing/wdt.xml', '_wdt' ); $routes->import( '@WebProfilerBundle/Resources/config/routing/profiler.xml', '/_profiler' ); } $routes->import(__DIR__.'/../src/App/Controller/', '/', 'annotation') }

app/AppKernel.phpGoodbye routing_dev.yml

Clarity & Control

@weaverryan

@weaverryan

protected function configureContainer(ContainerBuilder $c, $loader) { $loader->load(__DIR__ . '/config/config.yml'); $c->setParameter('secret', getenv('SECRET')); $c->loadFromExtension('doctrine', [ 'dbal' => [ 'driver' => 'pdo_mysql', 'host' => getenv('DATABASE_HOST'), 'user' => getenv('DATABASE_USER'), 'password' => getenv('DATABASE_PASS'), ] ]); // ...}

Environment Variables

@weaverryan

protected function configureContainer(ContainerBuilder $c, $loader) { $loader->load(__DIR__.'/config/config.yml'); if (in_array($this->getEnvironment(), ['dev', 'test'])) { $c->loadFromExtension('framework', [ 'profiler' => ['only_exceptions' => false] ]); } // ...}

Environment Control

@weaverryan

Build Services

protected function configureContainer(ContainerBuilder $c, $loader) { // ... $c->register('santa.controller', SantaController::class) ->setAutowired(true); }

@weaverryan

Build Routes

protected function configureRoutes(RouteCollectionBuilder $routes) { // ... $routes->add('/santa', 'AppBundle:Santa:northPole'); $routes->add(‘/naughty-list/{page}’, 'AppBundle:Santa:list') ->setRequirement('list', '\d+') ->setDefault('page', 1); }

@weaverryan

Bundless Applications?

@weaverryan

Wait, what does a bundle even give me?

A bundle gives you:

@weaverryan

1. Services 2. A resource root (e.g. path to load templates) 3. Magic functionality (e.g. commands) 4. Shortcuts

(_controller, AppBundle:User)

@weaverryan

1) Services

protected function configureContainer(ContainerBuilder $c, $loader) { // ... $c->register('santa.controller', SantaController::class) ->setAutowired(true); }

!

@weaverryan

2) Resource Root

2) Resource Rootprotected function configureContainer(ContainerBuilder $c, $loader) { // ... $c->loadFromExtension('twig', [ 'paths' => [__DIR__.'/Resources/views' => 'north_pole'] ]);}

public function randomAction($limit) { $number = rand(0, $limit); return $this->render(‘@north_pole/micro/random.html.twig’, [ 'number' => $number ]);}

!

@weaverryan

3) Magic Functionality

!

1. Register commands as services 2. Configure Doctrine mappings to load your

Entity directory

@weaverryan

4) Shortcuts

!

santa: controller: AppBundle:Santa:xmas controller: AppBundle\Controller\SantaController::xmasAction

$em->getRepository('AppBundle:App'); $em->getRepository('AppBundle\Entity\App');

@weaverryan

One New Trick

protected function configureRoutes(RouteCollectionBuilder $routes) { $routes->import(__DIR__.’@AppBundle/Controller/‘, '/', 'annotation') }

protected function configureRoutes(RouteCollectionBuilder $routes) { $routes->import(__DIR__.'/../src/App/Controller/', '/', 'annotation') }

Multiple Kernels?

@weaverryan

Multiple kernels, why?

@weaverryan

1. micro service architecture in monolithic repository

2. performance (less routes, services & listeners)

Multiple kernels was always possible

@weaverryan

Now they’re obvious

@weaverryan

// app/ApiKernel.php

class ApiKernel extends Kernel{ use MicroKernelTrait; public function registerBundles() { $bundles = array( new FrameworkBundle(), new SensioFrameworkExtraBundle() ); return $bundles; }}

No TwigBundle

class ApiKernel extends Kernel{ // ... protected function configureContainer($c, $loader) { $loader->load(__DIR__.'/config/config.yml'); $loader->load(__DIR__.'/config/api.yml'); }}

Use PHP logic to load share config, and custom config

class ApiKernel extends Kernel{ // ... protected function configureRoutes($routes) { $routes->import( __DIR__.'/../src/Api/Controller/', '/api', 'annotation' ); } public function getCacheDir() { return __DIR__.’/../var/cache/api/' .$this->getEnvironment(); }}

Load different routes

cacheDir ~= the cache key

Boot the correct kernel however you want

@weaverryan

// web/index.php use Symfony\Component\HttpFoundation\Request; require __DIR__.'/../app/autoload.php'; $request = Request::createFromGlobals();if (strpos($request->getPathInfo(), '/api') === 0) { require __DIR__.'/../app/ApiKernel.php'; $kernel = new ApiKernel('dev', true);} else { require __DIR__.'/../app/WebKernel.php'; $kernel = new WebKernel('dev', true);} $response = $kernel->handle($request); $response->send();

But how does it work?

@weaverryan

There is one personwho *hates* the name

MicroKernelTrait

@weaverryan

@weaverryan

@weaverryan

trait MicroKernelTrait{ abstract protected function configureRoutes(RouteCollectionBuilder $routes); abstract protected function configureContainer(ContainerBuilder $c, $loader); public function registerContainerConfiguration($loader) { $loader->load(function ($container) use ($loader) { $container->loadFromExtension('framework', array( 'router' => array( 'resource' => 'kernel:loadRoutes', 'type' => 'service', ), )); $this->configureContainer($container, $loader); }); } public function loadRoutes(LoaderInterface $loader) { $routes = new RouteCollectionBuilder($loader); $this->configureRoutes($routes); return $routes->build(); }}

Closure Loader

New service route loader

So what now?

@weaverryan

I have a big project…

@weaverryan

Use it for clarity

I’m teaching

@weaverryan

Show it for simplicity

I have a small app

@weaverryan

Show it for power

@weaverryan

PHP & Symfony Video Tutorials KnpUniversity.com

Thank You!