You know that all modern web applications and sites builds on frameworks. At the same time it is often hard to choose the right framework. If you are looking for a good instrument, I’m advice you to pay attention on Laravel and Symfony.

The last few years Laravel framework becames more and more popular and it is impossible do not pay attention on it. The results of Sitepoint.com survey at the end of 2013 shows that Laravel took first place among PHP frameworks and left behind Falcon and Symfony. That’s interesting: Laravel uses Symfony components inside and it shouldn’t wondering you because those components are very popular, it’s enough to remember DependencyInjection. At the same time Symfony framework is also successfully developing and doesn’t lose the leadership.

There are typical tasks which PHP programmers are faces every day, so I propose to look how those popular frameworks helps to accomplish such tasks on the blog example.

I need to say – I began to write this article before Laravel 5 official release, so in this article we are testing:

  • Laravel 4.2
  • Symfony 2.6

Step 1. Installing and run

I guess you agree that is great if you can easy and quickly to install framework on a local machine or a server and start coding? If a frameworks helps to do this – it makes your life and life of your team easier.

Laravel

Laravel documentation offers a few ways of installing: Laravel Installer, Composer, and also a simple cloning from the github repository. Since Composer has become de facto standard we will use composer:

php composer.phar create-project laravel/laravel test-laravel

We can run built-in server after installing with the command:

php artisan serve
Laravel development server started on http://localhost:8000

By opening http://localhost:8000 in the browser we will see that Laravel is ready for work:

Laravel PHP Framework

The installing is quite easy. Also we need to say that Laravel has such useful thing as Laravel Homestead, it lets you run ready environment for development. Homestead – is a simple virtual machine, made specially for Laravel. It includes all required tools: Ubuntu, PHP, nginx, mysql, even redis and memcached. If you want to run all this stuff you need also to install VirtualBox and Vagrant.

Symfony

Symfony Documentation also offers two ways for installing: Symfony Installer or Composer. We will use composer again:

php composer.phar create-project symfony/framework-standard-edition test-symfony

After installing Symfony offers to install AcmeDemoBundle, we can agree and enter “y” for the first time:

Would you like to install Acme demo bundle? [y/N] y 
Installing the Acme demo bundle

I would pay your attention that Symfony suggests you to create parameters.yml file where we can specify access to the database and another necessary params. I’m consider it’s very useful feature:

Creating the "app/config/parameters.yml" file
Some parameters are missing. Please provide them.
database_driver (pdo_mysql): 
...

Now we can run our application:

php app/console server:run

This command will run Symfony on the http://localhost:8000. Although, I had to run the application on the http://localhost:8001, because port 8000 is already used by Laravel :).

Now our application is available in the browser http://localhost:8001:

symfony-welcome

Resume

Installing Symfony as easy as installing Laravel, but I would pay tribute to Symfony because we already have configured connection to the database and we don’t need any manipulation there.

Step 2. Creating of entities (models)

The most common task in web application is storing objects to the database and reading out of there.

Laravel and Symfony suggests to developers ready ORM systems out of box. In the case of Laravel it’s Eloquent ORM and in the case of Symfony it’s Doctrine2 ORM. Eloquent and Doctrine using different patterns for accessing database. Eloquent implements Active Record template and Doctrine using quite famous Data MapperUnit Of Work and Identity Map.

Laravel

Laravel has no out of box command which creates database. If we want to create database we should do this manually.

Our Laravel models will look like:

Article – One To Many (one article may have many comments)

// app/models/Article.php

class Article extends Eloquent
{
    protected $fillable = ['title', 'text'];

    public function comments()
    {
        return $this->hasMany('Comment');
    }
}

Comment – Many To One (many comments might be connected to one article):

// app/models/Comment.php

class Comment extends Eloquent
{
    protected $fillable = ['title', 'text'];

    public function article()
    {
        return $this->belongsTo('Article');
    }
}

Then we need to run migration command which will create PHP class of the database schema for articles and comments:

php artisan migrate:make create_articles_table --table=articles --create=articles
php artisan migrate:make create_comments_table --table=comments --create=comments

Then we can add some fields to the migration file and run command again:

php artisan migrate

In order to create article and attach the comment we can do that in such way:

// app/routes.php
Route::get('articles/create', function() {
    $article = new Article();
    $article->title = 'Title at ' . date('Y-m-d h:i:s', time());
    $article->text = 'This is a simple text';
    $article->save();

    $comment = new Comment();
    $comment->user_name = 'John';
    $comment->text = 'some comment';

    $article->comments()->save($comment);

    return $article->id;
});

Symfony

Unlike Laravel, Symfony has commands for the creating/removing database:

In order to create a database we should run:

php app/console doctrine:database:create

By using command generate:doctrine:entity we can create entity, mention bundle, mapping cofiguration and properties which our entity should have.

php app/console generate:doctrine:entity

Article – One To Many relation

<?php

namespace Acme\DemoBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Article
 *
 * @ORM\Table(name="articles")
 * @ORM\Entity
 */
class Article
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;

    /**
     * @var string
     *
     * @ORM\Column(name="text", type="text")
     */
    private $text;

    /**
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="Comment", mappedBy="article")
     */
    private $comments;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     * @return Article
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set text
     *
     * @param string $text
     * @return Article
     */
    public function setText($text)
    {
        $this->text = $text;

        return $this;
    }

    /**
     * Get text
     *
     * @return string 
     */
    public function getText()
    {
        return $this->text;
    }

    /**
     * Set comments
     *
     * @param ArrayCollection $comments
     * @return Article
     */
    public function setComments($comments)
    {
        $this->comments = $comments;

        return $this;
    }

    /**
     * Get comments
     *
     * @return ArrayCollection
     */
    public function getComments()
    {
        return $this->comments;
    }
}

Comment – Many To One relation:

<?php

namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Comment
 *
 * @ORM\Table(name="comments")
 * @ORM\Entity
 */
class Comment
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="user_name", type="string", length=255)
     */
    private $userName;

    /**
     * @var string
     *
     * @ORM\Column(name="text", type="text")
     */
    private $text;

    /**
     * @var Article
     *
     * @ORM\ManyToOne(targetEntity="Article", inversedBy="Comments")
     */
    private $article;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set userName
     *
     * @param string $userName
     * @return Comment
     */
    public function setUserName($userName)
    {
        $this->userName = $userName;

        return $this;
    }

    /**
     * Get userName
     *
     * @return string 
     */
    public function getUserName()
    {
        return $this->userName;
    }

    /**
     * Set text
     *
     * @param string $text
     * @return Comment
     */
    public function setText($text)
    {
        $this->text = $text;

        return $this;
    }

    /**
     * Get text
     *
     * @return string 
     */
    public function getText()
    {
        return $this->text;
    }

    /**
     * Set article
     *
     * @param Article $article
     * @return Comment
     */
    public function setArticle(Article $article)
    {
        $this->article = $article;

        return $this;
    }

    /**
     * Get article
     *
     * @return Article
     */
    public function getArticle()
    {
        return $this->article;
    }
}

Also Symfony has such command as doctrine:generate:entities which we can run after mention of relations between the entities. This command will create an additional methods which we need for working with our relations. You should to use this command carefully because you can generate a huge number of methods that finally will became a dead code.

In order to store objects to the database we need to update database schema at first, so we need to run:

php app/console doctrine:schema:update --force

It is not recommended to use this command in production.

Symfony has no build-in instruments for managing database migrations and good practice is using DoctrineMigrationsBundle, which let you to create migrations of the database shema and also ability to rollback at necessary verision of the migration. We need to confess – it is not always possible.

In order to create our article in Symfony and attach a comment to it, we can use such code:

<?php

namespace Acme\DemoBundle\Controller;

use Acme\DemoBundle\Entity\Article;
use Acme\DemoBundle\Entity\Comment;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;

class ArticleController extends Controller
{
    /**
     * @Route("/article/create", name="_article_create")
     * @Template()
     */
    public function createAction()
    {
        $article = new Article();
        $article->setTitle('Simple title');
        $article->setText('Simple text');

        $comment = new Comment();
        $comment->setText('This is comment text');
        $comment->setArticle($article);
        $comment->setUserName('John');

        $em = $this->getDoctrine()->getManager();
        $em->persist($article);
        $em->persist($comment);
        $em->flush();

        return new Response('Article id: ' . $article->getId() . ', Comment id: ' . $comment->getId());
    }
}

Resume

  • Laravel has no instruments for the automatic updating of the database schema. When you are developing your model and adding a new properties you should also add each field to the migration class. As for me I would like to have more clever generator out of box.
  • Laravel let you generate restful controller, but again, this will be only a stub with the empty actions. If you want scafolding generator for creating a working chunk of the application look at popular Laravel-4-Generators which giving you ability to generate migrations, models, controllers and so on.
  • Symfony has more powerful generators out of box. Those generators may help you to create entities, update a database schema and cool scafolding tool will let you not just generate a controller with the empty actions but also a working chunk of the application. Command doctrine:generate:crud creates CRUD controller and a form based on Doctrine entity.
  • Laravel models looks quite elegant, but behind this magic and pithiness I think a big disadvantage is hidden. Models has no getters and setters and that is why we have no autocomplete in IDE – it makes coding not useful. In order to have code autocomplete in IDE we need to do some advanced manipulations: we can manually type a doc blocks “@property” for the classes or use the popular laravel-ide-helper, which let us to generate phpdocs to the models automatically.
  • Patterns using by Doctrine – it is a very flexible approach to the data sources. For example, one of the intersting way of using data source: in Doctrine we can create a custom repository that will not be associated with mysql database. Such repository may recieve entities from anywhere: from the local files, documents or from the remote services. Yes, maybe it’s strange and uncommon case, but sometimes that may has a sense. The main idea is to “forget” about data sources and simply implement the bussines logic.
  • Active Record, underlying in the Laravel models – it is of course a very useful way of working with the objects, but when the logic of storing model is lies in the model that may impose some restrictions in the future. From the other hand I don’t know about cases when it would be a problem.

Step 3. Service Container

I think you will agree that work with the dependencies and services – it is also one of the common tasks we always face. We are often need to create or use third-party services and it is important if a framework help us easy and fast working with them.

It is hard to explain anything in web development without looking to examples. Let’s simply see the ways of how frameworks suggest to work with the service container.

Laravel

Laravel giving us a few ways of how we can define a service, but a good practice it’s initialization of the service via service provider. Provider is useful when you want group your services in one place or if you just want to keep a code clear.

By the way I was upset a little when I had to spent much time to made my provider to work. The problem is that we need to cofigure autoloading for our services in the composer.json file.

Let’s create a service RssFeed:

<?php

namespace Acme\Rss;

class RssFeed
{
    public function getItems()
    {
        return ['item 1', 'item 2'];
    }
}

in RssProvider we will define service “rss.feed”

<?php
//app/Acme/Providers/RssProvider 
namespace Acme\Providers;

use Acme\Rss\RssFeed;
use Illuminate\Support\ServiceProvider;

class RssProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind('rss.feed', function() {
            return new RssFeed();
        });
    }
}

Also we need to add the namespace name of our service”Acme\Providers\RssProvider” to the file app/config.app.php in “providers” section.

Calling service:

Route::get('services', function() {
    $feed = App::Make('rss.feed');
    var_dump($feed->getItems());
});

Symfony

Symfony services are ussually located in bundles. For example in our AcmeBundle services are located in the src/Acme/DemoBundle/Resources/config/services.yml. Symfony let us to define service in YAML, XML, and PHP formats.

It is a quite easy to create service in YAML format:

# src/Acme/DemoBundle/Resources/config/services.yml
services:
    rss.feed:
        class: Acme\DemoBundle\Rss\RssFeed

Although, the YAML format is very pithiness it is better to define your services in XML. Using XSD schema, good IDE will automatically validate your Symfony services.

Calling our service in Symfony:

// src/Acme/DemoBundle/Controller/ArticleController.php

class ArticleController extends Controller
{
    /**
     * @Route("/rss", name="rss_feed")
     * @Template()
     */
    public function rssAction()
    {
        $feed = $this->get('rss.feed');

        return new Response(
            var_dump($feed->getItems())
        );
    }
}

If you want to create a service which will depends from another service (for instance, dependency of the Doctrine Entity Manager), you just need to use an arguments section:

# src/Acme/DemoBundle/Resources/config/services.yml
services:
    rss.feed:
        class: Acme\DemoBundle\Rss\RssFeed
        arguments: [ "@doctrine.orm.entity_manager" ]

At the same time you should to create constructor in the RssFeed class and pass EntityManager as argument:

class RssFeed
{
    /**
     * @var EntityManager
     */
    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function getItems()
    {
        return ['item 1', 'item 2'];
    }
}

Resume

  • Laravel and Symfony has good implementation of the service container. But I would point that Symfony demonstrates a more flexible way of working with the container. For example, when you are installing some third-party bundle, all you need to do is add bundle class to the app/AppKernel.php. After that you will have access to all services of this bundle.
  • One of the powerful components of Symfony it is Config component, it gives you opportunity to create cofiguration and validation for your bundle. It is a very useful when you are using a third-party bundle or when you are going to share your Open Source bundle with the community.

Step 4. Caching

Sooner or later we all face a problem with the optimization of our application. Of course, we want effectively to solve a problems of the caching with our framework.

Laravel

Out of box Laravel cache let you to serialize an arrays, objects and stores it in the filesystem. The good news is that we can use such popular caching backends like Memcached and Redis.

The simplest way to get our blog posts and cache them for 10 minutes:

// app/routes.php

Route::get('articles', function() {
    $articles = Cache::get('articles', function() {
        $articles = Article::all();
        Cache::add('articles', $articles, 10);

        return $articles;
    });

    foreach ($articles as $article) {
        var_dump($article->id, $article->title);
    }
});

The code above takes all articles and caching it for 10 minutes using a closure. When a time of caching will be expired a query to the database will be executed again.

Symfony

Symfony it is a framework which borrows caching model from the Giants. Caching models of HTTP validation and expiration are base for Symfony and based on famous HTTP specification.

If we are using Symfony Http Cache the site page is caching fully and all users will get a cached version during some time:


    /**
     * @Route("/articles", name="articles_index")
     * @Cache(expires="tomorrow", public=true)
     */
    public function articlesAction()
    {
        // code
    }

In this case Symfony will cache the page with articles for tomorrow when we visit “/articles” route.

And here more interesting case with the conditional cache invalidation:

    /**
     * @Route("/article/{id}", name="show_article")
     * @Cache(lastModified="article.getUpdatedAt()", ETag="'Article' ~ article.getId() ~ article.getUpdatedAt()")
     * @Template()
     */
    public function articleAction(Article $article)
    {
        return new Response($article->getTitle());
    }

In this case Symfony will not execute “articleAction” and always will return cached version of the page until article will not be updated. Yes, it will do each time one request to the database and maybe it is not a best solution for a highload applications. You can implement more smarter variant your self or you can try a powerful solution from the FriendsOfSymfony like FOSHttpCacheBundle.

Resume

  • I think Symfony provide more powerful and fundamental solution for caching. Thanks to annotations we have very useful way of cache managment. Support of HTTP validation and expiration model gives us an opportunity to increase site speed using browser cache and proxy servers like Nginx or Varnish.
  • We need to say that Laravel developers who use Laravel < 5 version also suggests solutions for the caching of whole page using Laravel filters, and Laravel 5 already supports ability to cache routing.

Шаг 5. Performance

I guess all developers will agree, performance – is one of the most important thing when choosing framework. In this test I used utility ab (Apache Benchmark). Symfony was running in prod environment. Cache was not used for both frameworks.

The main idea of test it’s just requesting “/articles” route where we are getting the all articles from database (10 articles at all) and display them in template.

ab -n 100 -c 100 http://localhost:8000/articles

The results

Laravel Symfony
Time taken for tests (sec) 0.958 8.438
Requests per second 104.43 11.85
Waiting for server response, min (ms) 22 101
Waiting for server response, median (ms) 475 4313
Waiting for server response, max (ms) 953 8434
Transfer rate (Kbytes/sec) 859.28 33.52

 

Resume

Although the frameworks were running in built-in PHP servers Laravel is about 10 times faster then Symfony. This small test shows us that if we will choose Symfony the problem of caching will be especially serios. The good news is we can optimize well our application using Http Cache. If we will use caching in Laravel we can reach even more perfomance, it is really fast framework.

Conslusion

I know my article not enough covers the topic. It possible to write a lot of important things which are greatly influence to choosing framework, but I have no enough time for this :) So I wrote about what I think is important at this moment.

Have you heard an opinion that every framework has own niche? I think Laravel is good for not big projects and Symfony it is a great choice for large projects and Enterprice applications.

But at the same time I think the choosing framework has one specific issue. let’s suppose you have choosed Symfony for your new project and got a good experience with this framework – so I’m doubt very much that you will have a free time and ability to learn Laravel for not big projects, isn’t it? Also if you have a great experience with Laravel, I think you won’t be want to learn Symfony to use it for a big projects. Looks like when developer or a team is choosing framework 99% they are choosing instrument which they will use for all type of projects.

Choosing framework is not a simple task and I hope this article was helpful. If you are choosing instrument for development and looking at Laravel or Symfony I wish you do not make a mistake!

By the way, have you already choosed framework? Looking for your comments!

Also don’t forget to subscribe it order to don’t skip the most intersting!