symfony: your next microframework (symfonycon 2015)
TRANSCRIPT
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!