PHP
Article

Introduction to Silex – A Symfony Micro-framework

By Younes Rafie

Silex is a PHP micro-framework based on Symfony components and inspired by the Sinatra Ruby framework. In this article, we are going to get started with the framework and see the how it fits our needs.

Logo

Installation

The best and recommended way to install Silex is through composer:

// composer.json
{
    "require": {
        "silex/silex": "1.3.*@dev",
        "twig/twig": "1.17.*@dev"
    },
    "require-dev": {
        "symfony/var-dumper": "dev-master"
    }
}

Run composer update --dev to load the dependencies and generate the autoloader. We also required twig because we want to use it as our template engine, and the new var-dumper from Symfony as a development dependency – read more about it here.

Creating a Folder Structure

One of the things I like about Silex is that it gives you a bare bones framework that you can organize in any way you want.

|-app/
|----config/
|-resources/
|----views/
|----logs/
|-src/
|----MyApp/
|-public/
|----index.php
|----.htaccess
|-vendor/
|-composer.json

For instance, I don’t like my root application folder to be called web, I prefer the normal public folder. The src directory is where we put our application specific code while the other folders are rather self explanatory.

Our public/index.php file will create a new Silex\Application which is our app container instance, and this is where we are going to wire the components.

// public/index.php
<?php

require_once __DIR__ . '/../vendor/autoload.php';

$app = new Silex\Application();

$app->run();

Now if you hit your root application URL, you should see a page not found error. You can turn on debugging by setting the debug mode in the container to true.

If you are having problems accessing your routes, make sure that your server document root is pointing to the public folder. You can check the doc for more info about configuring your webserver.

// public/index.php

//...

$app['debug'] = true;

$app->run();

Now if we try again we get a descriptive NotFoundHttpException, because we didn’t register any routes.

Routing

Registering a route is straightforward, you simply map a URL pattern to a controller function.

// public/index.php
$app->get('/', function(){
	return "Hello world";
});

We can handle get, post, put and delete, or we can use the match method to handle any request method. The handle function must return either a string or a Symfony\Component\HttpFoundation\Response instance.

// public/index.php
$app->get('/', function(){
	return new Symfony\Component\HttpFoundation\Response("Hello world");
});

Routing Parameters

$app->get("/users/{id}", function($id){
   return "User - {$id}";
});

We can add multiple parameters to the URL pattern, the only convention is that the name of the URL pattern parameter must match the name used in the function, or you’ll get a RuntimeException. You can also specify a URL pattern using the assert method, and a default value using the value method.

$app->get("/users/{id}", function($id){
   return "User - {$id}";
})
    ->value("id", 0) //set a default value
    ->assert("id", "\d+"); // make sure the id is numeric

One of my favorite route methods is convert: it allows us to intercept the request and change the parameter value before passing it to the callback function.

$app->get("/users/{user}", function($user){
    // return the user profile
    
    return "User {$user}";
})->convert("user", function($id){
        $userRepo = new User();
        $user = $userRepo->find($id);

        if(!$user){
            return new Response("User #{$id} not found.", 404);
        }

        return $user;
    });

In this example, the convert method takes a user id, looks up the database and returns the user. A 404 response is returned if the user is not found.

If you are a Laravel fan, you are used to filters like auth, csrf and guest. In Silex, however, you can provide a callback function to behave like a filter.

$app->get("/users/{user}", function($user){
    // return the user profile

    return "User {$user}";
})->before(function($request, $app){
    // redirect if the user is not logged in
})
->after(function($request, $response){
    // log request events
})
->finish(function(){
    // log request event
});

Similarly, you can use the after and finish methods. But keep in mind that the finish method doesn’t have access to the request and response because the response is already sent to the user.

Named Routes

When dealing with multiple routes it makes sense to name them descriptively. This can be helpful when updating the URL format or generating template links.

$app->get("/users/list", function(Silex\Application $app){
    return "List of users";
})->bind('users');

If you are using the URL Generator Service Provider, you can generate a link directly to the route.

<a href="{{ app.url_generator.generate('users') }}">Users</a>

Controllers

In real world applications, we don’t use closures for routing, but rather create separate controller classes to handle the requests.

$app->get("/", "MyApp\Controller\HomeController::index");

Grouping Controllers

One of the main benefits of using class controllers is the ability to group them. When creating a RESTful API, the URL’s will be something like this:

  • /users
  • /users/id [PUT, DELETE]
  • /users/id/edit

A really clean way to deal with this is to group the controllers into something called controller providers. Our User controller provider must implement the ControllerProviderInterface and define the connect method.

// src/MyApp/Controller/Provider/User.php

class User implements ControllerProviderInterface{

    public function connect(Application $app)
    {
        $users = $app["controllers_factory"];

        $users->get("/", "MyApp\\Controller\\UserController::index");

        $users->post("/", "MyApp\\Controller\\UserController::store");

        $users->get("/{id}", "MyApp\\Controller\\UserController::show");

        $users->get("/edit/{id}", "MyApp\\Controller\\UserController::edit");

        $users->put("/{id}", "MyApp\\Controller\\UserController::update");

        $users->delete("/{id}", "MyApp\\Controller\\UserController::destroy");

        return $users;
    }

}

The $app['controllers_factory'] returns a new Silex\ControllerCollection which holds our routing collection. The UserController will handle the registered requests.

// src/MyApp/Controller/UserController.php

class UserController{

    public function index(){
        // show the list of users
    }

    public function edit($id){
        // show edit form
    }

    public function show($id){
        // show the user #id
    }

    public function store(){
        // create a new user, using POST method
    }
    
    public function update($id){
        // update the user #id, using PUT method
    }

    public function destroy($id){
        // delete the user #id, using DELETE method
    }
}

The only remaining part is to attach our controller collection to our application. When working with routes, I prefer the Laravel approach of registering them inside a separate file and including them.

// app/routes.php

$app->mount("/users", new \MyApp\Controller\Provider\User());

The mount method takes the prefix and our User provider class as parameters.

Additionally, one of the benefits of using controller collections is the ability to use the before, after and finish filters without having to call them on every route.

// src/MyApp/Controller/Provider/User.php

class User implements ControllerProviderInterface{

    public function connect(Application $app)
    {
        //...
        $users->before(function(){
		// check for something here
	});
    }
}

Providers

We mentioned this term before but it simply stands for a small class that ties a component to the Silex application. There is a list of pre-included providers and, to use one, you simply register it to the application instance.

// app/providers.php
$app->register(new Silex\Provider\TwigServiceProvider(), array(
    'twig.path' => __DIR__.'/../views',
));

Of course this should be done after requiring Twig inside your composer and updating dependencies. Now if you visit the TwigServiceProvider::register method you can see that we can access the Twig_Environment from the container.

$app->get(function(){
   return $app['twig']->render('home.twig');
});

To create your own provider for Silex, you need to implement the Silex\ServiceProviderInterface and define the register method. You can read more in the doc.

Conclusion

Silex is a small and fast framework for Symfony fans. This introduction is aimed at getting you on board and trying the framework. We didn’t cover everything but hopefully I can make you exited to give it a go. If you have any questions or opinions let me know in the comments!

Younes Rafie
Meet the author
Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.
Comments
tjnine

Very nice article. Lately I have been looking into Slim and Silex and taking a break from the larger frameworks. This article cleared up many questions I had concerning controller providers and how to use them. Thank you very very much and PLEASE make another Silex article.

Best Regards,
TJ Nine

dariusz_tryba

I have a really basic problem setting this up. I can't manage to make the .htaccess working properly. I have the folder structure as in the example above, when I point the browser lo localhost/silex/public it shows the Hello World route just fine. But when I go to the localhost/silex/public/user/2 it says that there is no route found. Changing the #RewriteBase doesn't help. Can you please explain how to make this work so the app can be in a separate folder (I was only able to make it work when the index.php was in the main project's folder, not in the /public subfolder)?

younesrafie

Ideally, your apache virtual host should be pointing to your public folder, and the .htaccess file will use index.php as the main controller.
Try public/index.php/users/1. You can visit the doc for more info about configuring your web server.

swader

There will be more : )

TaylorRen

You see, that is what I mentioned in the Github PR. .htaccess is of great importance.

swader

Only with Apache. This is exactly why I prefer demos to be shown on Homestead Improved and similar unified environments which make sure nothing can go wrong, and why I'd rather see PHP's built-in server used for hosting demo apps locally than nginx and apache - server configuration is outside the scope of code demos, and only causes confusion due to the vast differences among readers' machines and configurations.

TaylorRen

Agreed but not totally agreed. For the purpose of this particular article, the right .htaccess is missing or not properly listed, which causes the confusion to one reader.

It will be ideal to have a unified environment but we have to agree that no one is actually using PHP built-in server to run a production app...

swader

Correct, but it's not feasible to supply a configuration file for every server, given that every reader may put his app in a different location and inside that location the index.php file into a different subfolder. It's an interesting thought, though. Maybe we should have a post describing the process of configuring Nginx and Apache for all the most popular frameworks and just link to it every time.

Michael_Brooks

I think that if you need a .htaccess file, then you should really be looking at tutorials for that. This is explaining about Silex and therefore shouldn't need any explanation about NGINX or Apache settings.

This is a pretty good tutorial and I feel that it doesn't need any explanation of where you should point your server's config files or place your htaccess as everyone does work differently.

If you were to look at any documentation for Laravel, Symfony, Silex etc, you will not find anything which goes into detail about how you do this as it is completely up to the user.

TaylorRen

Nope. Not at all. A tutorial, is to have the reader enabled to run it and try it, with not much additional external reference.

SF2 comes with a .htaccess file so it is not right to say find nothing on the details.

In particular, this tutorial says it does not like to use web folder as the entry folder, and changed to public. So it is worth mentioning.

zackw

I would like to see an article like this written for Slim as well. One of my favorite CMSes is likely moving to Slim so I may start using it in projects from now on.

swader

It's planned : )

diegovieira

at first I thought it was Slim by the router styling.

Alexander_Sidorov

Very nice article.
Where can I download the working files from the Silex example?

younesrafie

Thanks, I didn't put the example on Github because it doesn't contain too much code.

swader

Yeah, it wasn't anything too "publishable", being so simple. Just follow the instructions and you should have an identical copy within 5 minutes.

Dormilich

Note on some Service providers:

If you use the Validation Service and the Security Service, you need to register the Validation Service first, otherwise the user password validator doesn’t get added.

swader

Cool, thanks for the heads up

Dormilich

It would also make sense to extend Silex’s ControllerResolver to inject the DI container when creating router classes, as the container is (at that point) already available.

flow82

Hi there,
first, thanks a lot for the great tutorial. I was able to follow it easily, but when It comes to the controller-grouping part, things stopped working (for me).

I've copied the mentioned folder structure 1:1, but when I try to "mount" the controller:

$app->mount("/users", new \MyApp\Controller\Provider\User());

I get the error

Fatal error: Class 'MyApp\Controller\Provider\User' not found

It's not clear to me from where Silex "knows" that the controller-provider is in the folder src/MyApp/Controller/Provider/ (and - obvisiusly - it doesn't know it in my case wink)

So - simple or not - some working source code would be really great. It can help a lot to find little mistakes and get things working.
Many Thanks!

Dormilich

usually you set up an autoloader for your project. if you use composer you define that in composer.json’s autoload property.

"autoload": {
    "psr-4": {
        "MyApp\\": "src/MyApp/"
    }
},
flow82

Hi Dormilich,

thanks a lot, that put me on the right track. This link was very helpful:

[EDIT:cant insert link because I get error "Sorry, new users can only put 2 links in a post." but there was only one...strange.]

The User.php class can now be autoloaded, but gives me the following error:

fatal error: Interface 'ControllerProviderInterface' not found

So I've put

use Silex\ControllerProviderInterface;

at the top of Users.php, and end up with this error:

Fatal error: Declaration of User::connect() must be compatible with Silex\ControllerProviderInterface::connect(Silex\Application $app)

The Users.php looks like the following:

<?php
namespace MyApp;
use Silex\ControllerProviderInterface;

class User implements ControllerProviderInterface{
 
    public function connect(Application $app)
    {
        $users = $app["controllers_factory"];
        $users->get("/", "MyApp\\Controller\\UserController::index");
        $users->post("/", "MyApp\\Controller\\UserController::store");
        $users->get("/{id}", "MyApp\\Controller\\UserController::show");
        $users->get("/edit/{id}", "MyApp\\Controller\\UserController::edit");
        $users->put("/{id}", "MyApp\\Controller\\UserController::update");
        $users->delete("/{id}", "MyApp\\Controller\\UserController::destroy");
        return $users;
    }
}
?>

I tried to change public function connect(Application $app) to public function connect(Silex\Application $app), doesnt change anything...

Any ideas? I really want to get this running wink
Again, many thanks!

Dormilich

try connect(\Silex\Application $app), otherwise it gets the MyApp namespace prepended.

flow82

Thanks again.
Tried this, but then I get the

Fatal error: Class 'MyApp\Controller\Provider\User' not found

Looks like I'm missing something crucial here... :/

Dormilich

not sure where you defined that. the user class above resolves to MyApp\User.

flow82

Hi again,
I messed up my namespaces. Correcting them solved my issues.
This article was quite helpfull:
http://www.sitepoint.com/php-namespaces-import-alias-resolution/

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.