Contact Us

The PHP Duel: Symfony vs. Laravel | Toptal

Mobile App | May 27, 2021

Today, when starting a new project, one of the key decisions is to pick the right framework. It’s become hard to imagine building a complex web application from scratch nowadays without one.

Many popular languages for web development have their “default” framework, such as Ruby on Rails for Ruby, or Django for Python. However, PHP has no such single default and has multiple popular options to choose from.

According to Google trends and GitHub, the most popular PHP frameworks are Symfony with 13.7k stars and Laravel with 29k stars (at the time of writing this article).

In this article, I am going to compare these two frameworks and show you how to implement simple, everyday features with each. This way, you can compare the code of real-life examples side by side.

This article presumes strong PHP skills and an understanding of the MVC architectural paradigm, but no previous experience with Symfony or Laravel is required.

The Contenders

When speaking about Laravel, we are referring to Laravel version 4 and beyond. Laravel 4 was released in 2013 and represented a complete rewrite of the framework. The functionality of the framework was decoupled into separate components, which were managed with Composer, instead of everything being in one single huge code repository.

Laravel declares itself as a framework for rapid development with a simple and beautiful syntax which is easy to learn, read, and maintain. It is the most popular framework in 2016. According to Google trends, it is three times more popular than other frameworks, and on GitHub, it has two times more stars than competitors.

Symfony 2 was released in 2011, but it must not be confused with Symfony 1, which was a totally different framework with different underlying principles. Fabien Potencier created Symfony 2, and the current version is 3.2, which is an incremental version of Symfony 2. Therefore, they are often called simply Symfony2/3.

Like Laravel 4, Symfony 2 is designed as a set of decoupled components. There are two benefits here: We can replace any component in a Symfony project, and we can take and use any Symfony component in a non-Symfony project. Symfony components can serve as great code examples and they are used in a lot of open source projects such as Drupal, phpBB, and Codeception. In fact, Laravel itself uses no less than 14 Symfony components. Understanding Symfony thus gives you many benefits when working with other projects.

Framework Installations

Both frameworks come with installers and wrappers available via the PHP built-in web server.

Symfony Installation

Symfony installation is as simple as the following:

That’s it! Your Symfony installation is available on URL http://localhost:8000.

Laravel Installation

The Laravel installation process is almost the same and as simple as that for Symfony; the only difference is that you install Laravel’s installer through Composer:

You can now visit http://localhost:8000 and check your Laravel installation.

Note: Both Laravel and Symfony run off the same localhost port (8000) by default, so you can’t have these default instances running concurrently. Don’t forget to stop the Symfony server by running php bin/console server:stop before launching the Laravel server.

About Framework Installation

These are examples of a basic installation. For more advanced usage examples, such as being able to configure projects with local domains or run multiple projects at once, both frameworks provide Vagrant boxes:

Symfony uses YAML as the syntax for specifying its configuration. The default configuration is located in the app/config/config.yml file, and looks like the following example:

To create an environment specific configuration, create the file app/config/config_ENV.yml containing the basic config parameters. Here’s an example of a config_dev.yml file for the development environment:

This example turns on the web_profiler Symfony tool only for the development environment. This tool helps you to debug and profile your application right in the browser window.

In the configuration files, you can also notice %secret% constructions. They allow us to put environment-specific variables in the separate parameters.yml file. This file could be unique on every machine and is not stored under version control. For version control, we have a special parameters.yml.dist file that is the template for the parameters.yml file.

Here is an example of the parameters.yml file:

Laravel Basic Configuration

Laravel configuration looks very different from that of Symfony. The only one thing they have in common is that they both use files that are not stored under version control (.env in the Laravel case) and a template for generating this file (.env.example). This file has a list of keys and values, like the following example:

Like the Symfony YAML file, this one for Laravel is also human readable and looks clean. You can additionally create .env.testing file that will be used when running PHPUnit tests.

The application configuration is stored in .php files in the config directory. Basic configuration is stored in the app.php file and component-specific configuration is stored in <component>.php files (e.g., cache.php or mail.php). Here’s an example of a config/app.php file:

Framework Configuration: Symfony vs. Laravel

Symfony’s application configuration mechanisms allow you to create different files for different environments. Additionally, it prevents you from injecting complex PHP logic in the YAML configuration.

However, you may feel more comfortable with the default PHP configuration syntax Laravel is using and you don’t have to learn YAML syntax.

Routing and Controller

In general, a back-end web application has one primary responsibility: to read each request and create a response depending on the content of the request. The controller is a class responsible for transforming the request to the response by calling application methods, while the router is a mechanism that helps you to detect which controller class and method you should execute for a particular request.

Let’s create a controller that will show a blog post page requested from the /posts/{id} route.

Routing and Controller in Laravel



We’ve defined the route for GET requests. All requests with the URI matching the /posts/{id} will execute the BlogController controller’s show the method, and will pass the parameter id to that method. In the controller, we are trying to find the object of model POST with the passed id, and call Laravel helper view() to render the page.

Routing and Controller in Symfony

In Symfony, exampleController is a little bit bigger:

You can see we’ve already included @Route("/posts/{id}”) in the annotation, so we just need to include the controller in the routing.yml configuration file:

The step by step logic is the same as in the Laravel case.

Routing and Controller: Symfony vs. Laravel

At this stage, you might think Laravel is much nicer than Symfony. This is true, in the beginning. It looks way better and easier to start. However, in the real-life applications, you shouldn’t call Doctrine from the controller. Instead, you should call a service that will try to find the post or throw HTTP 404 Exception.

Laravel ships with a template engine called Blade and Symfony ships with Twig. Both template engines implement two main features:

Both features allow you to define a basic template with overridable sections and child templates that fill values of those sections.

Let’s consider the example of a blog post page again.

Laravel Blade Template Engine

Now you can tell Laravel in your Controller to render template post.blade.php. Do you remember our view(‘post’, …) call in the previous Controller example? You don’t need to know in your code that it is inherited from some other template. It is all defined only in your templates, on the view level.

Symfony Twig Template Engine

Templates: Symfony vs. Laravel

Structurally, Blade and Twig templates are quite similar. Both generate templates into PHP code and work fast, and both implement control structures, such as if statements and loops. The most important feature both engines have is that they escape the output by default, which helps prevents XSS attacks.

Aside from syntax, the main difference is that Blade allows you to inject PHP code directly into your templates and Twig does not. Instead, Twig allows you to use filters.

For example, if you want to capitalize a string, in Blade you would specify the following:

In Twig, on the other hand, you would do the following:

In Blade, it is easier to extend some functionality, but Twig does not allow any direct PHP code in templates.

Dependency Injection

Applications have a lot of different services and components, with various interdependencies. You need to store all the information about the created objects and their dependencies somehow.

Here comes our next component – Service Container. It is a PHP object that creates requested services and stores information about the created objects and their dependencies.

Let’s consider the following example: You are creating a class PostService to implement a method that is responsible for creating a new blog post. This class depends on two other services: PostRepository, which is responsible for storing information in the database, and SubscriberNotifier, which is responsible for notifying subscribed users about the new post. To get it to work, you need to pass these two services as the constructor arguments of PostService or, in other words, you need to inject these dependencies.

Symfony Dependency Injection Example

First, let’s define our sample services:

Next is the dependency injection configuration:

Now you can request your post service anywhere in the code from your Service Container object. For example, in the controller it could be something like this:

Service Container is a great component, and it helps to build your application following SOLID design principles.

Laravel Dependency Injection Example

It is much easier to manage dependencies in Laravel. Let’s consider the same example:

Here comes the beauty of Laravel – you do not need to create dependency configurations. Laravel automatically scans the dependencies for PostService in its constructor arguments types and automatically resolves them.

You can also use injection in your controller method to use PostService by “type-hinting” it in method arguments:

Dependency Injection: Symfony vs. Laravel

Laravel’s autodetection works great. Symfony has a similar capability called “autowire” that is turned off by default and could be turned on by adding autowire: true to your dependency configuration, but it requires some configuration. The Laravel way is simpler.

Object Relational Mapping (ORM)

To work with databases, both frameworks come with Object-Relational Mapping (ORM) features. ORM maps records from the database to objects in the code. To do this, you must create models for each record type (or each table) in your database.

Symfony uses a third-party project Doctrine to interact with the database, while Laravel uses its own library Eloquent.

The Eloquent ORM implements the ActiveRecord pattern to work with the database. In this pattern, each model is aware of the connection to the database and can interact with it. For example, it can save data to the database, update, or delete a record.

Doctrine implements the Data Mapper pattern, where models know nothing about the database; they are only aware of the data itself. A special separate layer, EntityManager, stores all the information about the interaction between models and databases, and it handles all the operations.

Let’s take an example to understand the difference. Let’s say your model has a primary id key, title, content, and author. The Posts table stores only the author id, so you need to create a relation to the Users table.

Let’s begin by defining the models:

Here, we created model mapping information and can now use a helper to generate the method stubs:

Next, we define post repository methods:

Now you can call these methods from the service or, for example, from PostController:

The User model ships with Laravel and it is defined by default, so you only need to define one model for the Post.

That’s all for models. In Eloquent, you don’t have to define model properties, as it builds them dynamically based on the database table structure. To store a new post $post into the database, you need to make this call (from the controller, for example):

To find all posts by an author with a given name, the best approach would be to find a user with its name and request all users’ posts:

ORM: Symfony vs. Laravel

With regard to ORM, Eloquent looks much more friendly for PHP developers and easier to learn than Doctrine.

Event Dispatcher vs. Middleware

One of the most important things to understand about a framework is its lifecycle.

Symfony and Event Dispatcher

To convert a request into a response, Symfony uses EventDispatcher. It consequentially fires different lifecycle events and special event listeners to handle these events. In the beginning, it dispatches the kernel.request event that includes request information. The main default listener of this event is RouterListener, which invokes the router component to find a suitable route rule for the current request. After this, other events are executed step-by-step. Typical event listeners are a Security check, CSRF token verification, and a logging process. If you want to add some functionality in the request lifecycle, you need to create a custom EventListener and subscribe it to the necessary event.

Laravel and Middleware

Laravel uses a different solution: middleware. I like comparing middleware to an onion: Your application has certain layers and a request passes through these layers on the way to the controller and back. So, if you want to extend your application logic and add some functionality in the request lifecycle, you need to add an additional layer to your middleware list, and Laravel will execute it.


Let’s try to create a basic CRUD example to manage a blog post:

REST API in Symfony

Symfony doesn’t have an easy out-of-the-box solution for fast REST API creation, but it has great third-party bundles FOSRestBundle and JMSSerializerBundle.

Let’s consider the minimal working example with FOSRestBundle and JMSSerializerBundle. After you installed them and turned them on in AppKernel, you can set in the bundle configuration that you will use JSON format and that this doesn’t have to be included in the URL requests:

In the routing configuration, you should specify that this controller will implement a REST resource:

You implemented a persist method in the repository in the previous example; now you need to add a delete method:

Next, you need to create a form class to accept input requests and map them to the model. You can do it by using a CLI helper:

You will receive a generated form type with the following code:

Now let’s implement our controller.

Note: the code I am going to show you is not perfect. It violates some design principles but could be easily refactored. The main purpose is to show you how to implement each method, step by step.

With FOSRestBundle, you don’t need to declare a route for each method; just follow the convention with controller method names, and JMSSerializerBundle will automatically convert your models to JSON.

REST API in Laravel

First, you need to define routes. You can do this in the API section of the route rules to turn off some default middleware components and turn on others. The API section is located in the routes/api.php file.

In the model, you should define the $fillable property to pass variables in the model’s create and update methods:

Now let’s define the controller:

In Symfony, you are using FosRestBundle, which wrapped errors in JSON. In Laravel, you need to do it yourself. You need to update the render method in the Exception handler to return JSON errors for expecting JSON requests:

REST API: Symfony vs. Laravel

As you can see, for a typical REST API, Laravel is much simpler than Symfony.

Picking the Winner: Symfony or Laravel?

There’s no clear winner between Laravel and Symfony, as everything depends on your final goal.

Laravel is a better choice if:

Symfony is the best option if:

This content was originally published here.