This course is archived!
Micro Symfony via MicroKernelTrait
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
How big is Symfony, really? It's HUGE! I'm kidding. Dude, Symfony is just a bunch of little libraries, so we can make it as small or as huge as we want. Most of us start with the Standard Edition: it looks basically like this, 10 or so configuration files, two front controllers, and some other skeleton files. Probably 20 or 30 files to begin with.
But no more in Symfony 2.8! Ok, the Standard Edition of course still exists, but now
we have a brand new tool in the arsenal: the MicroKernelTrait
. You can build a
Symfony app that's as small as one file, then let it grow from there.
Hey, should we try it?
Basic "Micro" Kernel
Imagine that we don't have this big project: we only have a composer.json
file
that requires symfony/symfony
:
{ | |
// ... lines 2 - 12 | |
"require": { | |
// ... line 14 | |
"symfony/symfony": "3.0.*@dev", | |
// ... lines 16 - 25 | |
}, | |
// ... lines 27 - 62 | |
} |
In the app directory, create a new class called LittleKernel
. The kernel is the
heart of your application: make it extend the base Kernel
class:
// ... lines 1 - 2 | |
use Symfony\Component\HttpKernel\Kernel; | |
class LittleKernel extends Kernel | |
{ | |
} |
Normally, this class must implement two abstract methods: registerBundles()
and
registerContainerConfiguration()
. But let's not implement those. Instead, use
the new MicroKernelTrait
:
// ... lines 1 - 2 | |
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; | |
// ... lines 4 - 9 | |
class LittleKernel extends Kernel | |
{ | |
use MicroKernelTrait; | |
// ... lines 13 - 24 | |
} |
This implements registerContainerConfiguration()
for us. All we need to do is
add registerBundles()
, configureRoutes()
and configureContainer()
:
// ... lines 1 - 2 | |
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; | |
use Symfony\Component\Config\Loader\LoaderInterface; | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
use Symfony\Component\HttpKernel\Bundle\BundleInterface; | |
use Symfony\Component\HttpKernel\Kernel; | |
use Symfony\Component\Routing\RouteCollectionBuilder; | |
class LittleKernel extends Kernel | |
{ | |
use MicroKernelTrait; | |
public function registerBundles() | |
{ | |
} | |
protected function configureRoutes(RouteCollectionBuilder $routes) | |
{ | |
} | |
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) | |
{ | |
} | |
} |
Ok, stop! Check this out, it's really cool. What is an application? Well, it's a set of bundles, a collection of services, and a list of routes. And would you look at that! We have exactly three methods that define the three things that make up a framework.
Single-File Symfony
Let's make a real-live app in just this one file. First, register some bundles:
// ... lines 1 - 2 | |
use Symfony\Bundle\FrameworkBundle\FrameworkBundle; | |
// ... lines 4 - 11 | |
class LittleKernel extends Kernel | |
{ | |
// ... lines 14 - 15 | |
public function registerBundles() | |
{ | |
return [ | |
new FrameworkBundle() | |
]; | |
} | |
// ... lines 22 - 38 | |
} |
We only need one bundle: FrameworkBundle
. To configure that bundle, head to
configureContainer()
, call $c->loadFromExtension()
, and pass it framework
and
an array of configuration. The only key it needs is secret
. Set it to the top
secret super cool-kids password: micr0
:
// ... lines 1 - 32 | |
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) | |
{ | |
$c->loadFromExtension('framework', [ | |
'secret' => 'micr0', | |
]); | |
} | |
// ... lines 39 - 40 |
And guess what? We already know exactly how this loadFromExtension()
works: it's
the PHP equivalent of having a config.yml
file with a framework
key at the root
and a secret
key below that:
// ... lines 1 - 10 | |
framework: | |
// ... lines 12 - 13 | |
secret: "%secret%" | |
// ... lines 15 - 61 |
Oh, and yea, you can still load YML and XML config files, and you probably will. I'll mention that soon.
At this point, this app will already work. But let's make a page. In configureRoutes()
,
we receive a shiny new RouteCollectionBuilder
object - another new thing for
Symfony 2.8 that makes adding routes in PHP a lot more fun than it used to be.
Add a route with $routes->add('/hello/symfony/{version}')
. The second argument
is the controller: pass it kernel:helloSymfony
:
// ... lines 1 - 27 | |
protected function configureRoutes(RouteCollectionBuilder $routes) | |
{ | |
$routes->add('/hello/symfony/{version}', 'kernel:helloSymfony'); | |
} | |
// ... lines 32 - 40 |
This is the service format for configuring a controller: kernel
is the name
of the service - that actually points to this object - and helloSymfony
is
the name of the method.
Hey, let's build that: public function helloSymfony()
. Give it a $version
argument
and say return new Response('Hi Symfony version '.$version);
:
// ... lines 1 - 22 | |
public function helloSymfony($version) | |
{ | |
return new Response('Hi Symfony version '.$version); | |
} | |
// ... lines 27 - 40 |
OMG. That's a fully-functional Symfony app in one file. The only thing we're missing is a front controller that boots and runs this guy. To be really trendy, we could put that code at the bottom of this file. But come on guys - I think we're letting this micro-framework fad get to our heads. Some structure is a good thing.
Adding the Front Controller
Instead, in the web directory, create a new file called tiny.php
. This will be our
version of an app.php
or app_dev.php
file.
Start by requiring Composer's autoloader and our LittleKernel.php
, since it
isn't configured to be autoloaded:
// ... lines 1 - 4 | |
require __DIR__.'/../vendor/autoload.php'; | |
require __DIR__.'/../app/LittleKernel.php'; | |
// ... lines 7 - 13 |
The rest of this file is just the same boring stuff we see in app_dev.php
. In fact, copy
the bottom of that file and paste it here. Change AppKernel
to LittleKernel
and make sure
the Request
class has its use
statement. Remove loadClassCache()
- a small performance
line - to make this even smaller:
// ... lines 1 - 2 | |
use Symfony\Component\HttpFoundation\Request; | |
// ... lines 4 - 7 | |
$kernel = new LittleKernel('dev', true); | |
$request = Request::createFromGlobals(); | |
$response = $kernel->handle($request); | |
$response->send(); | |
$kernel->terminate($request, $response); |
We're crazy!
Ready? Try it!
Over in the browser add /tiny.php
to the URL:
Ah! Hide from the error! Wait, check it out, it says:
No route found for "GET /" in...
Our app is working! We're using such a small bit of Symfony that we don't even have
the nice exception pages. You can get these back by adding TwigBundle
, but it's not
technically necessary.
Try the real page now: /hello/symfony/3
. There it is!
Creating a Bigger (Micro) App
A real app won't be just one file. But the MicroKernelTrait
allows you to grow
and opt into whatever features you want. Let's see if we can get our existing app
running. That means loading annotation routes from DefaultController
and booting
Twig to render the templates.
In LittleKernel
, add TwigBundle
and SensioFrameworkExtraBundle
to registerBundles()
:
// ... lines 1 - 2 | |
use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; | |
// ... lines 4 - 5 | |
use Symfony\Bundle\TwigBundle\TwigBundle; | |
// ... lines 7 - 13 | |
class LittleKernel extends Kernel | |
{ | |
// ... lines 16 - 17 | |
public function registerBundles() | |
{ | |
return [ | |
new FrameworkBundle(), | |
new TwigBundle(), | |
new SensioFrameworkExtraBundle() | |
]; | |
} | |
// ... lines 26 - 46 | |
} |
To activate Twig, we need more configuration under the framework
key. Add a
templating
key with an engines
sub-key. In there, add twig
in an array:
// ... lines 1 - 38 | |
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) | |
{ | |
$c->loadFromExtension('framework', [ | |
'secret' => 'micr0', | |
'templating' => ['engines' => ['twig']], | |
// ... line 44 | |
]); | |
} | |
// ... lines 47 - 48 |
That configuration looks a little "deep", but it's exactly what you've always had in
your config.yml
file:
// ... lines 1 - 10 | |
framework: | |
// ... lines 12 - 21 | |
templating: | |
engines: ['twig'] | |
// ... lines 24 - 61 |
Also activate the assets subsystem with an assets
key set to an empty array. This
makes it possible to use the asset()
function in Twig:
// ... lines 1 - 38 | |
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) | |
{ | |
$c->loadFromExtension('framework', [ | |
'secret' => 'micr0', | |
'templating' => ['engines' => ['twig']], | |
'assets' => [] | |
]); | |
} | |
// ... lines 47 - 48 |
Loading External Routing Files
Next, we need to load the annotation routes. That's super easy.
Use $routes->import()
. Pass it whatever routing resource you want, like a YAML
or XML file. In our case, import the annotations from the Controller
directory with
__DIR__.'/../src/AppBundle/Controller'
. Pass /
as the second argument - that's
the prefix - and annotation
- the "type" - as the last arg:
// ... lines 1 - 31 | |
protected function configureRoutes(RouteCollectionBuilder $routes) | |
{ | |
// ... lines 34 - 35 | |
$routes->import(__DIR__.'/../src/AppBundle/Controller', '/', 'annotation'); | |
} | |
// ... lines 38 - 48 |
This should feel familiar too: it's equivalent to importing routes in a YAML file
with resource
and type
keys:
app: | |
resource: "@AppBundle/Controller/" | |
type: annotation |
Really it's the exact same thing, just done in PHP instead.
We're done! But please, please don't get too excited, it's not going to work quite yet. Refresh anyways to see what's happening.
Annotations Loader
Error!
The annotation '@Sensio\Bundle\FrameworkExtraBundle\Configuration\Route' in method could not be auto-loaded.
If you work with annotations, then you need one little extra line of code. This line
already exists inside of Symfony's normal app/autoload.php
file:
// ... lines 1 - 2 | |
use Doctrine\Common\Annotations\AnnotationRegistry; | |
// ... lines 4 - 12 | |
AnnotationRegistry::registerLoader(array($loader, 'loadClass')); | |
// ... lines 14 - 16 |
In tiny.php
, require this autoload file. That gives us the annotation line we
need and still initializes Composer's autoloader:
// ... lines 1 - 4 | |
require __DIR__.'/../app/autoload.php'; | |
// ... lines 6 - 13 |
Refresh! It's alive!
Opting Into Features
Now check out the homepage. It's dead again!
Unknown "is_granted" function in base.html.twig at line 19.
Open that file and find line 19. Our app is choking on is_granted()
:
// ... lines 1 - 16 | |
<ul class="nav nav-pills pull-right"> | |
<li role="presentation" class="active"><a href="{{ path('homepage') }}">Home</a></li> | |
{% if is_granted('IS_AUTHENTICATED_FULLY') %} | |
<li role="presentation"><a href="{{ path('logout') }}">Logout</a></li> | |
{% else %} | |
<li role="presentation"><a href="{{ path('login') }}">Login</a></li> | |
{% endif %} | |
</ul> | |
// ... lines 25 - 39 |
This is one of the best and worst parts of the MicroKernel
. If you're accustomed
to having twenty bundles, then you're accustomed to having all the features of
Symfony. But, with the MicroKernel
you must opt into each feature. The is_granted()
comes from Symfony's SecurityBundle
... and we're not using that! If we need it, then
you'll need to add it to LittleKernel
and configure your security.yml
file.
For now, remove is_granted()
:
// ... lines 1 - 16 | |
<ul class="nav nav-pills pull-right"> | |
<li role="presentation" class="active"><a href="{{ path('homepage') }}">Home</a></li> | |
</ul> | |
// ... lines 21 - 35 |
And try again.
It alive... again! That's the MicroKernelTrait
in a nut shell: add as many bundles as
you want, configure them and add routing. You can still load external routing
and configuration files, and you should! The point isn't to put everything into
a single file: it's about starting with a single file and then choosing which bundles
and configurations make sense for your project.
Right now, I have my framework
configuration in PHP. But if the project grows,
I might want to use a YAML file instead. That's no problem.
// ... lines 1 - 38 | |
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) | |
// ... lines 40 - 48 |
See that $loader
variable? It has an import()
method on it - use that to import a
config.yml
file and you're done. This is the PHP-equivalent to the imports
line in YAML.
Go play with it: I hope you love it as much as I do!
Sorry for the spelling error but i am using MicroKernelTrait