This website uses cookies to ensure you get the best possible experience. See our Cookies Policy.

PMG Digital Made for Humans

Symfony from Scratch

7 MINUTE READ | May 20, 2015

Symfony from Scratch

symfony_black_01

In this post we’re going to build a Symfony full-stack app without Symfony Standard.

Would you do this every day? Probably not, but it’s a great way to learn a bit more about Symfony.

End Goals

The end goal here is to have an application that will send a simple Hello World message. So we’re going to cover the core framework stuff, but save things like templating, database access, ORMs, and forms for later. The goal here to see how to scaffold a Symfony app to better understand why symfony standard does what it does and where to deviate. We’ll end up with an app that uses the Symfony 3 directory structure.

The Smallest Composer.json

At the time of writing, Symfony 2.7 is on its second beta. We’ll be using that here. 2.7 is a LTS release and the rest of this tutorial should be relevant for some time to come.

Our composer.json is pretty tiny:

composer.json

{    "require": {        "symfony/symfony": "2.7.0-BETA2"    }}

The Application Kernel

Symfony apps all start their life by creating an new kernel object and then passing it around or handling HTTP requests with it. This class is usually called AppKernel which resides in the app directory at the root of your project and extends Kernel from the

HttpKernel
component.

There’s two methods that we have to implement on the kernel: registerBundles and registerContainerConfiguration. We’ll talk about both of those further down but leave them empty for now. We’re also going to do some customization here and point to var/log and var/cache for our log and cache directories.

AppKernel.php

<?phpuse Symfony\Component\HttpKernel\Kernel;use Symfony\Component\Config\Loader\LoaderInterface;final class AppKernel extends Kernel{    public function registerBundles()    {        $bundles = [            // we'll put stuff here later        ];        return $bundles;    }    public function registerContainerConfiguration(LoaderInterface $loader)    {            }    public function getLogDir()    {        return __DIR__.'/../var/log';    }    public function getCacheDir()    {        return __DIR__.'/../var/cache/'.$this->getEnvironment();    }}

composer2.json

{    "require": {        "symfony/symfony": "2.7.0-BETA2"    },    "autoload": {        "files": [            "app/AppKernel.php"        ]    }}

Handling Web Requests

Now let’s setup our front controller — a single PHP file that will handle all web requests that don’t go to static assets.

This is actually pretty simple: require the autoloader, create an instance of AppKernel, create a request from the super globals, then handle the request and send the created response.

index.php

<?phprequire __DIR__.'/../vendor/autoload.php';use Symfony\Component\HttpFoundation\Request;// 'dev' environment and load in debug mode$app = new AppKernel('dev', true);$request = Request::createFromGlobals();$response = $app->handle($request);$response->send();$app->terminate($request, $response);

Now let’s run the dev server with php -S localhost:8000 -t web/ and see what happens!

Fatal error: Uncaught exception ‘Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException’ with message ‘You have requested a non-existent service “http_kernel”.’

Well! Things are broken but we’re close.

What’s in a Bundle?

Think of a bundle as a distinct unit of functionality that can be be plugged into a Symfony kernel. A bundle begins its life as an implementation of BundleInterface and a special directory structure (that can be overridden of course). Bundles are loaded in the registerBundles method in AppKernel seen above. From there a bundle can load services into the container, add controllers and console commands, and do just about anything else.

What I think is the coolest part about symfony is that there is no real core. There are components that may depend on each other, but Symfony uses its own bundle system to glue together those components into the Symfony full stack framework.

A big chunk of that glue is FrameworkBundle. It contains all the services necessary to actually handle web requests, include the missing http_kernel service in the error message above.

Let’s add it and try our web request again.

AppKernel2.php

<?phpfinal class AppKernel extends Kernel{    public function registerBundles()    {        $bundles = [            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),        ];        return $bundles;    }    // ...}

If you fire up the dev server again you’ll going to get another error message about a missing service.

Stepping into Configuration

The error from above is because we have not done any configuration yet. This is where the registerContainerConfiguration comes in. It takes a configuration loader as its argument and you use that to load up configuration from Yaml, XML, or PHP files. We’ll use Yaml here.

When you create an instance of AppKernel, you do so with an environment. In terms of the Symfony components and bundles, the environment does very little. You as a user have the choice of using it. In fact, there’s already a bit of its use in getCacheDir above.

Configuration is another popular spot to use environments. Generally folks will have a config_{environment}.yml for each environment used. Those files import the main config.yml file. All configuration resides in app/config by convention. We’re only going to use one environment here, dev, so we’ll only create that file and a main configuration. We’re also going to create an empty (for now) routing.yml file.

Our main configuration is pretty simple. Notice the stuff wrapped in percentage signs. Those are parameters and will be replaced with values a parameters file or the environment. I’m a big fan putting config in the environment rather than using a parameters file.

config.yml

framework:    secret: %secret%    router:        resource: "%kernel.root_dir%/config/routing.yml"        strict_requirements: ~

The dev configuration imports the main config and declares a parameters section to make our development a bit easier (no environment variables). This works for the purposes of this example, but some things are better off in parameter files for development (like a set of database creds).

config_dev.yml

imports:    - { resource: config.yml }parameters:    secret: sshhh

Now we need to load our configuration in AppKernel.

AppKernel3.php

<?phpuse Symfony\Component\HttpKernel\Kernel;use Symfony\Component\Config\Loader\LoaderInterface;final class AppKernel extends Kernel{    // ...    public function registerContainerConfiguration(LoaderInterface $loader)    {        $loader->load(sprintf('%s/config/config_%s.yml', __DIR__, $this->getEnvironment()));    }    // ...}

Now when we run the dev server we can exciting error:

Symfony\Component\HttpKernel\Exception\NotFoundHttpException: No route found for “GET /”

We’re close! All that’s left is to add some routes.

AppBundle

AppBundle is the container for all of your application’s integration with Symfony. It’s a place where you put controllers, forms, etc. Anything that’s not core business logic goes in AppBundle.

We’re going to autoload our AppBundle from src and register it in AppKernel by creating a class that extends Bundle and instantiating it in registerBundles.

composer3.json

{    "require": {        "symfony/symfony": "2.7.0-BETA2"    },    "autoload": {        "psr-4": {            "PMG\\FromScratch\\": "src/"        },        "files": [            "app/AppKernel.php"        ]    }}

AppBundle.php

<?phpnamespace PMG\FromScratch\AppBundle;use Symfony\Component\HttpKernel\Bundle\Bundle;final class AppBundle extends Bundle{    // noop}

AppKernel4.php

<?phpfinal class AppKernel extends Kernel{    public function registerBundles()    {        $bundles = [            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),            new PMG\FromScratch\AppBundle\AppBundle(),        ];        return $bundles;    }    // ...}

Hello, World

We are finally ready to add a controller in the AppBundle.

HelloController.php

<?phpnamespace PMG\FromScratch\AppBundle\Controller;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;final class HelloController extends Controller{    public function helloAction(Request $r)    {        return new Response(            sprintf('Hello, %s', $r->get('name') ?: 'World'),            200,            ['Content-Type' => 'text/plain']        );    }}

To register the controller with our router, we’ll edit the empty routing.yml file from above.

routing2.yml

hello:    path: /    defaults: { _controller: AppBundle:Hello:hello }

Finally we can spin up the dev server and see a beautiful Hello, World.

Code for This Tutorial

Insights meet inbox

Sign up for weekly articles & resources.

All the code for this tutorial can be found in this GitHub repo. The commits are in the order of the tutorial so you can step through things and watch the application grow.


Posted by Christopher Davis

Related Content

thumbnail image

Get Informed

PMG Innovation Challenge Inspires New Alli Technology Solutions

4 MINUTES READ | November 2, 2021

Get Informed

Applying Function Options to Domain Entities in Go

11 MINUTES READ | October 21, 2019

thumbnail image

Get Informed

My Experience Teaching Through Jupyter Notebooks

4 MINUTES READ | September 21, 2019

Get Informed

Trading Symfony’s Form Component for Data Transfer Objects

8 MINUTES READ | September 3, 2019

Get Inspired

Working with an Automation Mindset

5 MINUTES READ | August 22, 2019

Get Informed

Parsing Redshift Logs to Understand Data Usage

7 MINUTES READ | May 6, 2019

Get Inspired

3 Tips for Showing Value in the Tech You Build

5 MINUTES READ | April 24, 2019

thumbnail image

Get Informed

Testing React

13 MINUTES READ | March 12, 2019

Get Inspired

Tips for Designing & Testing Software Without a UX Specialist

4 MINUTES READ | March 6, 2019

Get Informed

A Beginner’s Experience with Terraform

4 MINUTES READ | December 20, 2018

BACK TO ALL POST