Buy Access to Course
01.

Bootstrapping Micro-Symfony

Share this awesome video!

|

Inspiration strikes! You've just come up with a crazy awesome idea - the kind of idea that's sure to save the world and impress your friends all at once. Now all you need to do is start coding.

Of course, Symfony - the fully-featured full-stack framework will be perfect for this. Then again, what if we used Silex? The micro-framework approach might be a better way to get started so the world can quickly find out what its been missing!

But Silex isn't Symfony: it lacks a lot of the tools. And if your project grows, it can't easily evolve into a Symfony app: they're just too different.

There's another option: use Symfony as a micro-framework, meaning with as few files, directories and complications as possible, without sacrificing features. And since no official Symfony micro-framework exists right now, let's create one.

Composer Bootstrap

Start with a completely empty directory: so empty that we need to run composer init to bootstrap a composer.json file:

composer init

Fill in the name - none of this matters too much in a private project. Ok, dependencies. We only need one: symfony/symfony. But I also want one more library: sensio/framework-extra-bundle - this is for annotation routing. If you don't want that, you only need symfony/symfony.

Our blank project now has 1 file - composer.json - with just 2 dependencies:

14 lines | composer.json
{
"name": "knpuniversity/micro-symfony",
"require": {
"symfony/symfony": "^2.7",
"sensio/framework-extra-bundle": "^3.0"
},
// ... lines 7 - 12
}

Go back to run composer install:

composer install

Now our empty project has a vendor/ directory.

Bootstrapping Symfony

Now that we have Symfony how do we bootstrap the framework? We need two things: a kernel class and a front controller script that boots and executes it.

Start with the kernel: create a new AppKernel.php file at the root of your project. You can call this file anything, but I want to stay consistent with normal Symfony when possible. Make this extend Symfony's Kernel class. Since we're not in a namespaced class, PhpStorm auto-completes the namespaces inline. Ew! Add a use statement instead: let's keep things somewhat organized people:

24 lines | AppKernel.php
// ... lines 1 - 2
use Symfony\Component\HttpKernel\Kernel;
// ... lines 4 - 5
class AppKernel extends Kernel
{
// ... lines 8 - 22
}

Kernel is an abstract class. Use cmd+n - or the menu option, Code->Generate - then "Implement Methods". Select the two methods at the bottom:

24 lines | AppKernel.php
// ... lines 1 - 2
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel
{
public function registerBundles()
{
// ... lines 10 - 16
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
// ... line 21
}
}

registerBundles()

Get rid of the comments. So first, let's instantiate all the bundles we need. In a normal, new, Symfony project, there are 8 or more bundles here by default. We just need 3: a new FrameworkBundle, TwigBundle - this is used to render error and exception pages, so you may want this, even if you're building an API. And since I'll use annotations for my routing, use SensioFrameworkExtraBundle. If you like YAML routing, you don't even need this last one. Return the $bundles:

24 lines | AppKernel.php
// ... lines 1 - 5
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
);
return $bundles;
}
// ... lines 18 - 22
}

registerContainerConfiguration()

For the next method - move the inline namespace to be a use statement on top. When the kernel boots, it needs to configure each bundle - it needs things like the database password, where to log, etc. That's the purpose of registerContainerConfiguration().

Use $loader->load() and point this to a new config/config.yml file:

24 lines | AppKernel.php
// ... lines 1 - 5
class AppKernel extends Kernel
{
// ... lines 8 - 18
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config.yml');
}
}

And now create the config directory and that file so that it isn't pointing into outerspace. Leave it blank for now.

Front Controller: index.php

That's a functional kernel! We need a front controller that can instantiate and execute it. Create a web/ directory with an index.php inside. Symfony has two front controllers: app.php and app_dev.php: we'll have just one because I want less less less. We're going for the micro of micro!

In my browser, we're looking at the symfony/symfony-standard repository, because I want to steal some things. Open the web/ directory and find app_dev.php. Copy its contents into index.php. Now we're going to slim this down a bit:

31 lines | web/index.php
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
// If you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
//umask(0000);
// This check prevents access to debug front controllers that are deployed by accident to production servers.
// Feel free to remove this, extend it, or make something more sophisticated.
if (isset($_SERVER['HTTP_CLIENT_IP'])
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|| !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')
) {
header('HTTP/1.0 403 Forbidden');
exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
$loader = require_once __DIR__.'/../app/bootstrap.php.cache';
Debug::enable();
require_once __DIR__.'/../app/AppKernel.php';
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

Delete the IP-checking stuff and uncomment out the umask - that makes dealing with permissions easier. For the loader, require Composer's standard autoloader in vendor/autoload.php. And AppKernel is not in an app/ directory - so update the path:

24 lines | web/index.php
// ... lines 1 - 4
umask(0000);
// ... lines 6 - 10
$loader = require_once __DIR__.'/../vendor/autoload.php';
require_once __DIR__.'/../AppKernel.php';
// ... lines 13 - 24

When you create the kernel, you pass it an environment and whether or not we're in debug mode: that controls whether errors are shown. We'll tackle these properly soon, but right now, hardcode two new variables $env = 'dev' and $debug = true. Pass these into AppKernel:

24 lines | web/index.php
// ... lines 1 - 7
$env = 'dev';
$debug = true;
// ... lines 10 - 17
$kernel = new AppKernel($env, $debug);
// ... lines 19 - 24

Oh, and put an if statement around the Debug::enable() line so it only runs in debug mode. This wraps PHP's error handler and gives us better errors:

24 lines | web/index.php
// ... lines 1 - 13
if ($debug) {
Debug::enable();
}
// ... lines 17 - 24

And in the spirit of making everything as small as possible, remove the loadClassCache() line: this adds a small performance boost. If you're feeling less micro, you can keep it.

Testing it out

That's it for the front controller! Time to try things. Open up a new terminal tab, move into the web directory, then start the built-in web server on port 8000:

cd web/
php -S localhost:8000

Let's try it - we're hoping to see a working Symfony 404 page, because we don't have any routes yet. Okay, not working yet - but this isn't so bad: it's a Symfony error, we're missing some kernel.secret parameter.

Adding the Base Config

The reason is that we do need a little bit of configuration in config.yml. Add a framework key with a secret under it that's set to anything:

7 lines | config/config.yml
framework:
secret: ABC123
// ... lines 3 - 7

When we refresh now, it's a different error: something about a Twig template that's not defined. These weird errors are due to some missing configuration. Here's the minimum you need. Add a router key with a resource sub-key that points to a routing file. Use %kernel.root_dir%/config/routing.yml. %kernel.root_dir% points to wherever your kernel lives, which is the app/ directory in normal Symfony, but the root folder for us minimalists.

You also need a templating key with an engines sub-key set to twig:

7 lines | config/config.yml
framework:
secret: ABC123
router:
resource: '%kernel.root_dir%/config/routing.yml'
templating:
engines: [twig]

That's all you need to get things working. Oh, and don't forget to create a config/routing.yml file - it can be blank for now:

1 lines | config/routing.yml

Refresh! It lives! "No route found for GET /" - that's our working 404 page. We'll fix that missing CSS in a bit, but this is a functional Symfony project: it just doesn't have any routes yet.

The Symfony Plugin

One of the coolest things about using Symfony is the PhpStorm plugin for it, which we cover in the Lean and Mean dev with PHP Storm tutorial.

If you have it installed, search "symfony" in your settings to find it. Check "Enable" and remove app/ from all the configurable paths. Our project is smaller than normal: instead of app/cache and app/logs, you just have cache and logs at the root. Init a new git repository and add these to your .gitignore file along with vendor/:

4 lines | .gitignore
/vendor/
/cache/
/logs/

Count the files: other than composer stuff, we have 1, 2, 3, 4 files, and a functioning Symfony framework project. #micro