Symfony2: as good as PHP gets?

💬 5

I've been getting my hands dirty with Symfony2 of late. At the start of the year, I was introduced to it when I built an app using Silex (a Symfony2 distribution). The special feature of my app was that it allows integration between Silex and Drupal 7.

More recently, I finished another project, which I decided to implement using Symfony2 Standard Edition. Similar to my earlier project, it had the business requirement that it needed tight integration with a Drupal site; so, for this new project, I decided to write a Symfony2 Drupal integration bundle.

Overall, I'm quite impressed with Symfony2 (in its various flavours), and I enjoy coding in it. I've been struggling to enjoy coding in Drupal (and PHP in general) – the environment that I know best – for quite some time. That's why I've been increasingly turning to Django (and other Python frameworks, e.g. Flask), for my dev projects. Symfony2 is a very welcome breath of fresh air in the PHP world.

However, I can't help but think: is Symfony2 "as good as PHP gets"? By that, I mean: Symfony2 appears to have borrowed many of the best practices that have evolved in the non-PHP world, and to have implemented them about as well as they physically can be implemented in PHP (indeed, the same could be said of PHP itself of late). But, PHP being so inferior to most of its competitors in so many ways, PHP implementations are also doomed to being inferior to their alternatives.

Pragmatism

I try to be a pragmatic programmer – I believe that I'm getting more pragmatic, and less sentimental, as I continue to mature as a programmer. That means that my top concerns when choosing a framework / environment are:

Symfony2 definitely gets more brownie points from me than Drupal does, on the pragmatic front. For projects whose data model falls outside the standard CMS data model (i.e. pages, tags, assets, links, etc), I need an ORM (which Drupal's field API is not). For projects whose business logic falls outside the standard CMS business logic model (i.e. view / edit pages, submit simple web forms, search pages by keyword / tag / date, etc), I need a request router (which Drupal's menu API is not). It's also a nice added bonus to have a view / template system that gives me full control over the output without kicking and screaming (as is customary for Drupal's theme system).

However, Symfony2 Standard Edition is a framework, and Drupal is a CMS. Apples and oranges.

Django is a framework. It's also been noted already, by various other people, that many aspects of Symfony2 were inspired by their counterparts in Django (among other frameworks, e.g. Ruby on Rails). So, how about comparing Symfony2 with Django?

Although they're written in different languages, Symfony2 and Django actually have quite a lot in common. In particular, Symfony2's Twig template engine is syntactically very similar to the Django template language; in fact, it's fairly obvious that Twig's syntax was ripped off from inspired by that of Django templates (Twig isn't the first Django-esque template engine, either, so I guess that if imitation is the highest form of flattery, then the Django template language should be feeling thoroughly flattered by now).

The request routing / handling systems of Symfony2 and Django are also fairly similar. However, there are significant differences in their implementation styles; and in my personal opinion, the Symfony2 style feels more cumbersome and less elegant than the Django style.

For example, here's the code you'd need to implement a basic 'Hello World' callback:

In Symfony2

app/AppKernel.php (in AppKernel->registerBundles()):

<?php
$bundles = array(
    // ...

    new Hello\Bundle\HelloBundle(),
);

app/config/routing.yml:

hello:
    resource: "@HelloBundle/Controller/"
    type:     annotation
    prefix:   /

src/Hello/Bundle/Controller/DefaultController.php:

<?php
namespace Hello\Bundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    /**
     * @Route("/")
     */
    public function indexAction()
    {
        return new Response('Hello World');
    }
}

In Django

project/settings.py:

INSTALLED_APPS = [
    # ...

    'hello',
]

project/urls.py:

from django.conf.urls import *

from hello.views import index

urlpatterns = patterns('',
    # ...

    url(r'^$', index, name='hello'),
)

project/hello/views.py:

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello World")

As you can see above, the steps involved are basically the same for each system. First, we have to register with the framework the "thing" that our Hello World callback lives in: in Symfony2, the "thing" is called a bundle; and in Django, it's called an app. In both systems, we simply add it to the list of installed / registered "things". However, in Symfony2, we have to instantiate a new object, and we have to specify the namespace path to the class; whereas in Django, we simply add the (path-free) name of the "thing" to a list, as a string.

Next, we have to set up routing to our request callback. In Symfony2, this involves using a configuration language (YAML), rather than the framework's programming language (PHP); and it involves specifying the "path" to the callback, as well as the format in which the callback is defined ("annotation" in this case). In Django, it involves importing the callback "callable" as an object, and adding it to the "urlpatterns" list, along with a regular expression defining its URL path.

Finally, there's the callback itself. In Symfony2, the callback lives in a FooController.php file within a bundle's Controller directory. The callback itself is an "action" method that lives within a "controller" class (you can have multiple "actions", in this example there's just one). In Django, the callback doesn't have to be a method within a class: it can be any Python "callable", such as a "class object"; or, as is the case here, a simple function.

I could go on here, and continue with more code comparisons (e.g. database querying / ORM system, form system, logging); but I think what I've shown is sufficient for drawing some basic observations. Feel free to explore Symfony2 / Django code samples in more depth if you're still curious.

Funny language

Basically, my criticism is not of Symfony2, as such. My criticism is more of PHP. In particular, I dislike both the syntax and the practical limitations of the namespace system that was introduced in PHP 5.3. I've blogged before about what bugs me in a PHP 5.3-based framework, and after writing that article I was accused that my PHP 5.3 rants were clouding my judgement of the framework. So, in this article I'd like to more clearly separate language ranting from framework ranting.

Language rant

In the PHP 5.3+ namespace system:

Framework rant

In Symfony2:

In summary

Let me repeat: I really do think that Symfony2 is a great framework. I've done professional work with it recently. I intend to continue doing professional work with it in the future. It ticks my pragmatic box of supporting me in building a maintainable, well-documented, re-usable solution. It also ticks my box of avoiding reverse-engineering and manual deployment steps.

However, does it help me get the job done in the most efficient manner possible? If I have to work in PHP, then yes. If I have the choice of working in Python instead, then no. And does it help me avoid frustrations such as repetitive coding? More-or-less: Symfony2 project code isn't too repetitive, but it certainly isn't as compact as I'd like my code to be.

Symfony2 is brimming with the very best of what cutting-edge PHP has to offer. But, at the same time, it's hindered by its "PHP-ness". I look forward to seeing the framework continue to mature and to evolve. And I hope that Symfony2 serves as an example to all programmers, working in all languages, of how to build the most robust product possible, within the limits of that product's foundations and dependencies.

Post a comment

💬   5 comments

Matt Robinson

Django was my first web framework, and I still have fondness for it and Python (and its sexy sexy template language). One small hole to poke in your routing complaint:

The Router component only matches the incoming URL on the Request object to its URL map, and sets attributes on the Request. One of those attributes is called _controller, and as far as the Symfony kernel is concerned, the only requirement it has is that the ControllerResolver can turn it into a callable (if it's not one already). Silex's compact Sinatra-like syntax is just syntactic sugar which creates a Route object with the given callable as its _controller attribute, then adds it to the router's URL map.

The damage is caused by the alternative ControllerResolver supplied by FrameworkBundle, which can only handle bundle:controller:method, service:method, or classname::method string. It's pretty simple to replace though (I think!). The default HttpKernel ControllerResolver can take any callable, classname::method, function name or closure name (or rather, any class with an __invoke() method).

That said, just because it's possible, I think it's reasonable for you to say that this isn't easy in Symfony2.

Overall, I'm surprised you didn't stick with Silex, given your opinions on things like YAML, efficiency, and Symfony's conventions. Silex is just Symfony without all the conventions.

Jaza

@Matt Robinson: interesting - thanks for pointing out that under the hood, Symfony2 does actually have the concept of a generic callable, and that it can handle any callable as a request handler - I didn't realise that. I haven't delved into Symfony2's internals much yet.

I've also used Silex, and yes, I like that the standard way of writing a callback in Silex is as an anonymous function passed directly to $controllers->get(). You have a point, Silex does a better job of fully exposing the underlying components' ability to handle any type of callable, whereas the Framework Bundle somewhat sabotages this.

Re: why didn't I stick with Silex? Mainly because many third-party Symfony2 extensions are written only as "bundles" for the Standard Edition, and not as "service providers" for Silex. I.e. many are built to work out-of-the-box with the Standard Edition, whereas significant work is needed to make them play nice with Silex.

Also, in my opinion, the default setup for Silex feels insufficient once a project's codebase grows beyond a certain size (because by default all controllers are in a single file, code files aren't sufficiently modularised / organised, etc). On the other hand, the Standard Edition feels like overkill for my projects, in terms of the modularisation / organisation that it encourages in its default setup. So, I guess I have yet to see a Symfony2 distribution that sits in a good middle ground in this respect.

Matt Robinson

Mm, I get the point about bundles. In my experience, which could just be luck, most of the bundles I've needed have actually wrapped 3rd party framework-independent libraries, so I've been able to just use those directly (usually by writing a slim service-provider). Packagist.org is a great resource for that.

Couple of things you might want to look at in that case, are yolophp (which is a sort of joke / teaching aid whose name still makes me wince; but is a reimplementation of Silex using only Symfony components, so potentially capable of loading some bundles), and @Fabpot's article on packing the Symfony2 framework into a single file; you don't have to go that far, but it has some interesting info on what parts of SF2 you can strip out to leave a leaner framework that might suit your needs better.

I agree that the default setup for Silex isn't great for growth, but then it's a micro-framework: you get the bare minimum out of the box and the rest is up to you. It can be daunting not having a structure defined for you, but it can also be liberating. I've done a LOT of refactoring on my Silex projects as the projects I've written become better defined and/or I've learned or thought of better ways to do things.

Back to Silex. It's totally possible to make large applications with Silex; the ServiceControllerServiceProvider is one way to start. You can pick a structure that suits the scale of your project. I've got one big Silex project going now, and the structure I picked for it was to select discrete blocks of functionality and package them as Controller & Service providers (i.e. register() sets up the services, and the boot() method calls $app->mount('/', $this->connect($app)) which loads the routes, so entire blocks of the site can be enabled, developed and maintained fairly separately). You could also use StackPHP's UrlMapper and LazyLoadingHttpKernel to split your app into multiple small apps and only load the one you need. ConfigServiceProvider can load config files based on an environment variable, which is also very handy. Good luck!

Matt Robinson

Oh! And the Symfony RAD Edition might also be worth a look-in, although it still feeds on YAML files, so may not really be your thing.

Titouan Galopin

Hello!

Interesting post but I wanted to correct some big mistakes here:

You have to specify the "namespace path" using the "namespace" declaration at the top of every single file in your project that contains namespaced classes ...

Learn the difference between PHP namespaces and Python packages. PHP never told anyone that namespaces should be mapped to a directory structure, and that's where the PHP implementation is great: a namespace is just a box for a bunch of code. A single file could contain all classes of the namespace, PHP does not care. It's because of its use with spl_autoloader_register that the system is great, but you don't have to use it. So it's coherent to not do such automatic determination of the namespace.

You can only import namespaces using their absolute path, resulting in overly verbose "use" declarations all over the place; wheras in other (saner) languages relative (and wildcard) namespace imports are possible

Just false. Learn a bit about PHP:

use Doctrine\Some\AbsoluteNamespace\Path;
$class = new Path\SomeClass();

You're able to define configuration (e.g. routing callbacks) in multiple formats, with the preferred format being YAML (although raw PHP configuration is also possible) ...

And in which framework don't you have to learn things before to use it? It's a matter of choices, YAML is just much better for raw configuration where XML is great for services definitions and PHP for dynamic configuration. It's because of the possible uses of Symfony that many formats exist.

Only a class method can be a routing callback, a class itself or a stand-alone function cannot be a callback, as the routing system is too tightly coupled with PHP's class- and method-based namespace system

False too. A routing callback can be any function. The case about the class is just stupid: how would you map a route to a complete class?!

An overly complex and multi-levelled directory structure is needed for even the simplest projects ...

Try to do a real big project once, and you will get why it's really great to have such a structure. Symfony is for large profesionnal projects, Try to use an adapted framework for your projects.

I worked a lot with Django, CodeIgniter, Zend Framework. I took a glimpse to a lot of web framework in various languages. And until now, I found that the best one are clearly Symfony and Ruby on Rails. For big projects, they are just the bests.

I don't like the "mode" to dislike PHP because it is "badly thought". That's just not true. PHP has in mind to be as intuitive to use as possible. That means that sometimes, as not everyone has the same intuition, you would find things unnatural. However, on the other hand, it's MUCH easier to create a web projet in PHP than it is in Ruby.

I could talk a lot about it, and I know PHP is not the best language in the world (clearly :) ), but this article's arguments are the wrong ones.