Web front-end
9 minute read

Why I Decided To Embrace Laravel

Alexei is a senior web developer with 8+ years of experience. He has architected entire projects & has worked as a Scrum Master.

Over the last year, I took part in three large projects. My task was to move away from the old architecture based on PHP and server-side HTML generation, and transition to REST API.

With the old approach, back-end developers were expected to know much more about the UI and visual aspects of the application. Because of this, they had to pay attention to different segments of the application, instead of focusing on their primary objective. Having the back-end API strictly separated from the UI allowed our developers to focus on the quality of their code.

Also, testing API services is much easier as REST API can be verified by automated unit testing.

Why I Decided To Embrace Laravel

I’ve had some experience in writing my own framework, as well as working with Yii, CakePHP, CodeIgniter, Slim Framework, Symfony and few other open source frameworks. Each time, I’ve experienced a lack of functionality or awkward approach to some problems.

I used Laravel for four months before deciding to choose it as the platform for our next project. The project itself was a great success and this article is a product of this experience. Now I am able to call myself a Laravel developer.

Why I Chose Laravel

I’ve already outlined some of my reasons for using Laravel and my experience, so let’s take a closer look at what made Laravel a better choice for my latest project:

  • Quick and functional core that can be extended
  • Clean and simple routing
  • Effective ORM and database layer
  • Easy integration with third-party libraries (AWS, export libs, etc.). You can use Composer or Packagist to include libraries in your project
  • Active and growing community that can provide quick support and answers
  • Supporting unit tests out of the box
  • Async queue and background jobs for the long running tasks

Laravel Core And Routing

The Laravel kernel is hosted on GitHub. The kernel implements an IoC pattern allowing customization and rewriting of any part of the framework (request, logging, authentication, etc.).

Laravel designers didn’t spend too much time reinventing the wheel. A lot of solutions and practices are transferred from other frameworks. A good example of this approach is the extended Symfony console called Artisan, which is a command-line interface included with Laravel.


Routing in Laravel is amazing; it is very similar to the Ruby on Rails (RoR) implementation, which I like a lot. You can easily group routes, create resources for CRUD pages, attach filters and automatically bind models to the request parameters.

Nested routes are a very useful feature:

Route::group(['prefix'=>'level0'], function(){
   	Route::get('/', array('uses' => '[email protected]'));
    Route::group(['prefix'=>'/{level0}/level1'], function(){
   	    Route::get('/', array('uses' => '[email protected]'));
	    Route::post('/{custom_variable}/custom_route', array('uses' => '[email protected]_route'));

Versioning can be implemented as a group on the top level for all nested routes by using the ‘v1’ prefix. When we change the API version, we can retain the old one and use the ‘v2’ prefix to start implementation of routes with new code and logic, i.e., new references to controllers and actions.

Let’s take a step-by-step look at everything used in this Laravel tutorial:

A defined group of routes with path level0 at top level of our API.

If we have a call BASEURL/level0, then Laravel will resolve it and call method level0() of TestController to process the request.

We have a sub-group with the {level0}/level1 pattern. To access API resources inside this group we should use the path for the parent group (level0) and match the pattern of our sub-group ({level0}/level1). For example, level0/777/level1 is the correct path for this sub-group of our API. Here we have 777 as the variable level0 that Laravel will path to the handler of the routes inside the sub-group.

In the the end we have two sample routes:

BASEURL/level0/777/level1 - GET request to this URI will be processed with method level1($level0) of TestController where the first parameter is {level0} and it will be initialized with value 777.

BASEURL/level0/777/level1/888/custom_variable - POST request to this URI will be processed using the custom_route($level0, $custom_variable) method of CustomController.

The parameters used in the method come from the route variables.

Finally, we can associate our variable {level0} in the route with a model, i.e., LevelModel. In this case, the framework automatically tries to find existing database records and pass it on as a parameter to the controller’s method.

It helps us write less code and we don’t need to write LevelModel::find($id) or LevelModel::findOrFail($id) in controllers.

The Dingo API package takes routing one step further.

Here are a few features that Dingo provides to the framework:

  • Transformers: Special objects for response customization (e.g., type-cast integers and boolean values, paginate results, include relationships, etc.)
  • Protect routes and allow different/custom authentication providers. Controller Trait provides protection of custom actions, while we can easily retrieve authenticated user info with API::user().
  • Limiting request count per user by using built-in or custom throttles.
  • A powerful internal requests mechanism that allows requests on your API internally

Laravel uses Eloquent ORM

Laravel is based on Eloquent ORM. I’ve used it with PostgreSQL and MySQL, and in both cases it performed flawlessly.

The official documentation is comprehensive, so there is no reason to repeat things in this article:

Query Scope – query logic is created as a function with a special scope prefix.

The following example shows the standard SQL Query transformed to a query function in Eloquent ORM:

SELECT * WHERE hub_id = 100 AND (name LIKE `˜%searchkey%` OR surname LIKE `˜%searchkey%`): 
function scopeByFieldListLike($query, $fields, $value){
    $query->where(function($query) use ($fields, $value){
        foreach($fields as $field){
            $query->orWhere($field , 'like', "%".$value."%");
    return $query;

Using a function in your code is straightforward.

$model = new User;
$model->byFieldListLike(['name', 'surname'], 'searchkey');

A lot Eloquent methods return an instance of the QueryBuilder. You can use almost all of these methods on models.

Unit Testing

Writing unit tests is usually very time consuming, however, it’s definitely worth the time, so please do it.

Laravel relies on a TestCase base class for this task. It creates a new instance of the application, resolves the route and runs the controller method in its own sandbox. However, it does not run application filters (App::before and App::after) or more complex routing scenarios. To enable these filters in a sandbox environment, I had to manually enable them by using Route::enableFilters().

Obviously, if you know how to use Laravel, you know it could use some more work in the unit testing segment. I’ve set up a few functions that helped me create more advanced unit tests. Feel free to take these and use them in your projects.

To perform more advanced real-life testing and implement “curl-like” requests, I used the kriswallsmith/buzz library. This provided me with a much needed feature set for testing, including custom headers and uploading files.

The code below shows the function you can use to execute this kind of test:

public function browserRequest($method, $resource, $data = [], $headers = [], $files = [])
    $host = $this->baseUrl();
    if (count($files)){
        // Use another form request for handling files uploading
        $this->_request = new Buzz\Message\Form\FormRequest($method, $resource, $host);
        if (isset($headers['Content-Type'])) {
            // we don't need application/json, it should form/multipart-data
        foreach($files as $file) {
            $upload = new Buzz\Message\Form\FormUpload($file['path']);
            $this->_request->setField($file['name'], $upload);
        $response = new Buzz\Message\Response();
        $client = new Buzz\Client\FileGetContents();
        $client->setTimeout(60);//Set longer timout, default is 5
        $client->send($this->_request, $response);
    } else {
        $this->_request = new Buzz\Message\Request($method, $resource, $host);
        $response = new Buzz\Message\Response();
        $client = new Buzz\Client\FileGetContents();
        $client->setTimeout(60);//Set longer timout, default is 5
        $client->send($this->_request, $response);
    return $response;

I am a very lazy developer, so I added optional parameters that allowed me to decode responses, extract data, and check the response codes. This led to another method that includes default headers, authorization tokens and some additional functionality.

public function request($method, $path, $data = [], $autoCheckStatus = true, $autoDecode = true, $files = [])
    if (!is_array($data)){
        $data = array();
    $servers = ['Content-Type' => 'application/json'];
    if ($this->_token){
        $servers['authorization'] = 'Token ' . $this->_token;
    $this->_response = $this->browserRequest($method, $path, $data, $servers, $files);
    if ($autoCheckStatus === true){
    } elseif(ctype_alnum($autoCheckStatus)){
        $this->assertEquals($autoCheckStatus, $this->_response->getStatusCode());
    if ($autoDecode){
        $dataObject = json_decode($this->_response->getContent());
        if (is_object($dataObject))
            // we have object at response
            return $this->_dataKeyAtResponse && property_exists($dataObject, $this->_dataKeyAtResponse) ? $dataObject->{$this->_dataKeyAtResponse} : $dataObject;
        elseif (is_array($dataObject))
            // perhaps we have a collection
            return $this->_dataKeyAtResponse && array_key_exists($this->_dataKeyAtResponse, $dataObject) ? $dataObject[$this->_dataKeyAtResponse] : $dataObject;
            // Uknown result
            return $dataObject;
    } else {
        return $this->_response->getContent();

Once again, my lazy habits made me add wrappers designed to test CRUD pages:

create($data = [], $autoCheckStatus = true, $autoDecode = true, $files = [])
show($id, $data = [], $autoCheckStatus = true, $autoDecode = true)
update($id, $data = [], $autoCheckStatus = true, $autoDecode = true, $files = [])
delete($id, $data = [], $autoCheckStatus = 204, $autoDecode = false)
index($data = [], $autoCheckStatus = true, $autoDecode = true)

A simple test code would look like this:

// send wrong request
// Validation error has code 422 in out specs
$response = $this->create(['title'=>'', 'intro'=>'Ha-ha-ha. We have validators'], 422);
// Try to create correct webshop
$dataObject = $this->create(
    $data = [
        'title'=>'Super webshop',
        'intro'=>'I am webshop',
$this->assertGreaterThan(0, $dataObject->id);
$this->assertData($data, $dataObject);

// Try to request existing resource with method GET
$checkData = $this->show($dataObject->id);
// assertData is also a help method for validation objects with nested structure (not a part of PHPUnit).
$this->assertData($data, $checkData);

// Try to update with not valid data
$this->update($dataObject->id, ['title'=> $data['title'] = ''], 422);

// Try to update only title. Description should have old value. Title - changed
$this->update($dataObject->id, ['title'=> $data['title'] = 'Super-Super SHOP!!!']);

// Then check result with reading resource
$checkData = $this->show($dataObject->id);
$this->assertData($data, $checkData);
// Read all created resources
$list = $this->index();
$this->assertCount(1, $list);
// TODO:: add checking for each item in the collection
// Delete resoure
$this->show($dataObject->id, [], 404);

Here is another useful function for validating response data including relations and nested data:

public function assertData($input, $checkData){
// it could be stdClass object after decoding json response
    $checkData = (array)$checkData;
    foreach($input as $k=>$v){
        if (is_array($v) && (is_object($checkData[$k]) || is_array($checkData[$k]))) {
    	    // checking nested data recursively only if it exists in both: response data($input) and expected($checkData)
           $this->assertData($v, $checkData[$k]);
        } else {
           $this->assertEquals($v, $checkData[$k]);


Long running tasks are a common bottleneck in web applications. A simple example would be the creation of a PDF report and its distribution inside an organisation, which can take a lot of time.

Blocking user requests to perform this action is not a desirable way of addressing the issue. Using Queue in Laravel is a great way of handling and queuing long running tasks in the background.

One of the last issues was PDF generation and sending reports via email. I used a default queue driver for Beanstalkd, supported with the package pda/pheanstalk. It is very easy to add tasks to the queue

For example, generating and sending PDFs could be done like this:

\Queue::push('\FlexiCall\Queue\PdfSend', [....data for sending to job's handler..], 'default');

Our PdfSend handler could be implemented like this:

class PdfSend extends BaseQueue{
    function fire($job, $data){
    	//..... OUR IMPLEMENTATION.....

Laravel specifications suggest tracking a queue with --daemon options, but I use the supervisor daemon that keeps it running permanently:

php artisan queue:listen --env=YOUR_ENV

Queues are great for tasks like saving data. They can be used to repeat failed jobs, add sleep timeouts before executing jobs, etc.

Putting It All Together

I’ve mentioned few key points related to Laravel development in this Laravel review. How you put it all together and build your application will depend on your specific setup and project. However, here is a brief checklist of steps you might want to consider:

  • Setup homestead Vagrant box prepared for development using Laravel and configured local environment.
  • Add endpoints (configure routes).
  • Add authentication layer.
  • Add filters before/after application execution, giving you an option to run anything before and after application is executed. Additionally, you can define filters for any route.

My default “before” filter creates a global scope as a singleton and starts the timer for monitoring performance:

	    class AppBefore {
			function filter($request) {
	    	    \App::singleton('scope', function() {
            		$scope = new AppScope();//My scope implementation
	    	        return $scope;

The “after” filter stops the timer, logs it to the performance log, and sends CORS headers.

		class AppAfter{
		    function filter($request, $response){
        		$response->header('Access-Control-Allow-Origin', '*');
		        $response->header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
        		$response->header('Access-Control-Allow-Headers', 'Content-Type');
		        $response->header('Access-Control-Max-Age', '86400');
        		return $response;

Developing each of your modules will likely require these steps:

Setup data migration, and seed default data. Implement models and relations, transformers, validators and sanitizers (if needed). Write unit tests. Implement controllers and execute tests.

Wrap Up

I’ve been using Laravel for over a year. As with any technology, there were some teething problems, but I believe that learning Laravel has convinved me that it is an amazing framework. There are a few more things that I haven’t mentioned in this Laravel tutorial, but if you are interested in learning more, you may want to consider these points and features:

  • Powerful model for subscribing and listening to application events.
  • Support for Amazon SDK aws/aws-sdk-php.
  • Caching.
  • Own templating engine called Blade, if you like building the application “the old way” without RESTful backend and UI separation.
  • Simple configuration.
  • Built-in Stripe billing module.
  • Out of the box localization.
  • Catching and handling most errors.

Good luck and stay tuned, Laravel is a promising framework and I believe it will be around for years to come.


ALIF Ahmed
<a href="http://www.alifitsolutions.in/laravel">make website in laravel</a>
Tony Brown
Laravel makes PHP fun like Rails did for Ruby. I don't get to play with Node.js or Rails or Django, I'm stuck working with PHP, thank goodness for Laravel and Composer
Yep, exactly. I have been loving it after touching RoR, but I didn't have time to learn long time one more script language and then I met Laravel. Have 4th project with it.
Development ApoloIt
Hi, really nice article, thanks for sharing your experience. I have a question, what about performance? I see some benchmarks that says that slim si 3x faster than laravel for REST Api and I can use composer to "attach" Eloquent to Slim. It is so bad as they say? What is your experience about that?
Right now I am working on perfomance in the current project. We have a lot of db queries on some requests (conversations/message with recipients+attachments) and it takes a long time. I am implementing cache layer as decorator for the model (not all, mostly used). If you think about perfomance of laravel framework - it's execellent. I have used slim too and laravel has more powerful core, in my opinion. So, after attaching Eloquent to Slim you will have same bottleneck: working with database. If you will have 50-100 queries to db per request - it doesn't matter what framework you use (slim/symfony/laravel). I don't recomend to use a lot of business logic in the models. Later you can put data to the cache on higher level (controller or transformer of response).
Redj Chester
Oh my gosh, I don't want to be rude but ... Laravel! Really? Some mistakes, misleades and bad practicies spoted.
Ale Vasta
Very nice article ! thank you ! what do you think about Phalcon Framework ? if you get a chance to answer, know your opinion could be great. I am just choosing a FW to start a large project. I've been reading the phalcon's documentation and i think it is a good alternative for this kind of projects. Thanks again
Bertrand Kintanar
mistakes, misleads, and bad practices: can you state specifics and explain why it is a mistake, a mislead, and a bad practice and can you state what should be the correct, not misleading, and good practice? Not to be rude as well but saying something that there is something wrong, but not pointing out which and not stating a solution is not helping anyone. :)
I believe, that phalcon is fast and powerful solution. I don't like approach, that it's a php extension as I had bad experience with using 3rd-party extensions in past. The projects are still alive (5-6 years), but authors don't support builds for all platforms and new versions of php. It stucks with migration or need more time for rewriting everything from scratch ($$$ question for customer). I would like to debug core of framework and figure out how it works. Using laravel sources (Session/Cache etc.) I have learned how it works and implement some of my solutions as external libs. Also I don't like to configure servers and prefer easy deployment process. Phalcon looks like as a good alternative, you can achieve success with it if you are ready to keep in mind his advantages and disadvantages. maybe it is best suited for you and your development process
Have to admit the article didn't fill me with enthuasim, the examples etc., were difficult to follow. Maybe the article was written for those use to Larvael, but as a casual reader just looking to see if this might make life easier, then it unfortunately fails! Sorry!
Giancarlo Ventura Granados
Laravel is good, but I prefer Symfony
Aini khan
Laravel next generation PHP framework love it enjoy it code in it make me a real programmer. I am Laravel evangelist
Steven Parker
Laravel is one of the best PHP Frameworks available for web development. The Framework is first of all open source, and it has numerous great features, as well as a lot of extensions, and an overall elegant feel about it.
Does Laravel do server side rendering? I am trying to do that with react. I love Laravel for the REST API I can make with it.
comments powered by Disqus