Since its initial release in 2011, Laravel’s usage and developers’ interest in it grew exponentially, establishing it as the most popular open-source PHP framework currently available. Contributing factors to this were its extremely rich feature set, excellent documentation, code elegancy, and on top of it all, incredible simplicity.
While from the developer’s perspective, all of this sounds great—and it really is—Laravel development’s flat learning curve means employers need to take extra steps to ensure they’re hiring a part-time or full-time developer capable of delivering quality, scalable code. This article will focus on introducing Laravel’s extensive feature set as well as some core programming concepts which will equip interviewers to construct effective questions and recognize developers with adequate experience and expertise for their project requirements.
Concepts
Before we dive into Laravel specifics, we’re going to introduce a few programming and web application concepts. It’s important interviewers are familiar with these concepts because we’re going to be referring to them later, and understanding them will also enable you to gauge a potential employee’s programming knowledge.
Model-view-controller (MVC)
MVC is a software design pattern. Simply put, it dictates that all application code should be separated into three layers:
- The data layer (model) stores and/or prepares data based on the controller’s instructions.
- The presentation layer (view) displays to the end user the data received from the controller.
- The “glue” layer (controller) handles incoming requests and instructs the data layer on what to do with the request data. Based on the request, it gets the data layer to prepare new data, which is then sent to the presentation layer for rendering.
The Laravel framework is based around the MVC pattern, but it does not enforce developers’ use of MVC. This means that a developer not being familiar with MVC and not honoring best practices can result in poorly written code, regardless of the framework.
Let’s look at examples of poorly written and well-written code:
<html>
<head>
</head>
<body>
<?php
$pdo = new PDO('mysql:host=localhost;dbname=laraveldb', 'root', 'pass');
$query = $pdo->query('select * from articles');
$results = $query->fetchAll(PDO::FETCH_ASSOC);
?>
<?php foreach ($results as $articleData): ?>
<div>
<h2><?php echo $articleData['title']; ?></h2>
<p><?php echo $articleData['content']; ?></p>
</div>
<?php endforeach; ?>
</body>
</html>
In this example, we interact with the database directly in our presentation layer. We also don’t have a model for the articles table, or a controller which would send data to the view. Let’s rewrite this in Laravel.
The Article
model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
//
}
Our controller, which will pass all of the Article
s to the view:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Article;
class ArticleController extends Controller
{
public function index()
{
return view('articlesList', ['articles' => Article::all()]);
}
}
Now, the $articles
variable will be available in our articlesList
view:
<html>
<head>
</head>
<body>
@foreach ($articles as $article)
<div>
<h2>{{ $article->title }}</h2>
<p>{{ $article->content }}</p>
</div>
@endforeach
</body>
</html>
Each part of the code is now separated into its respective layer, making it clean and maintainable.
Note: This example uses syntax like Article::all()
and @foreach
that will be explained later in the guide.
Dependency Injection
Dependency injection is a software design concept which states that a client (object) should only know which dependencies (e.g., services) it needs to perform certain functions, but should not know who supplies these dependencies or how they’re constructed. This allows for loose coupling, which makes the code more flexible and less prone to errors. We’ll talk more about this later.
Let’s take a look at an example:
class Article
{
protected $contentFormatter;
protected $content;
public function __construct($content)
{
$this->contentFormatter = new ContentFormatter;
$this->content = $content;
}
public function getFormattedContent()
{
return $this->contentFormatter->format($this->content);
}
}
$article = new Article('Lorem Ipsum Content');
$formattedContent = $article->getFormattedContent();
We can see how the Article
class is responsible for instantiating the ContentFormatter
, when all it should really care about is providing the formatted content. Let’s implement dependency injection:
class Article
{
protected $contentFormatter;
protected $content;
public function __construct($contentFormatter, $content)
{
$this->contentFormatter = $contentFormatter;
$this->content = $content;
}
public function getFormattedContent()
{
return $this->contentFormatter->format($this->content);
}
}
$contentFormatter = new ContentFormatter;
$article = new Article($contentFormatter, 'Lorem Ipsum Content');
$formattedContent = $article->getFormattedContent();
This might not seem like a big improvement at the moment, but it will become much more apparent how this technique can solve architectural problems and make the code easier to maintain once we’ve covered the Service Container section of this guide.
Object-relational Mapping (ORM)
ORM is a technique for converting data from relational database tables into objects, and vice-versa. Once constructed, these objects can then manipulate data via the API provided by the ORM implementation they’re using, and save it into the database. This greatly simplifies interacting with the database through code, and in most cases, does not require writing any SQL at all.
Here are some examples of how this works:
Article::where('is_active', 1)->get();
// select * from articles where is_active = 1
Article::create([
'title' => 'Some Article Title',
'content' => 'Lorem ipsum content'
]);
// insert into articles (title, content) values
// ("Some Article Title", "Lorem ipsum content")
User::whereIsNull('email')->delete();
// delete from users where email is null
Laravel Core Architecture
By now, using everything we’ve talked about so far, managers should be able to craft interview questions that will give insight into a developer’s understanding of some core programming concepts, and help weed out those who lack the experience to build the company’s next amazing project. But what about Laravel’s specific architecture and features?
Service Container
The service container is the main component of Laravel’s dependency injection implementation. It provides several ways of registering different types of bindings. Perhaps the most commonly used container’s feature is binding an interface to a given implementation.
app()->bind(
'App\Contracts\PaymentGateway',
'App\Services\StripePaymentGateway'
);
This code instructs the container to inject StripePaymentGateway
when PaymentGateway
service is required by a class:
class PaymentController
{
protected $paymentGateway;
public function __construct(PaymentGateway $paymentGateway)
{
$this->paymentGateway = $paymentGateway;
}
}
Provided our PaymentController
is resolved from the service container, its $paymentGateway
property will be an instance of StripePaymentGateway
. Remember the loose coupling we mentioned earlier? This is it. We’ve decoupled the client from the PaymentGateway
implementation, which means, if at some point we decide to replace Stripe with some other payment gateway, we can just bind PaymentGateway
interface to our new SomeOtherPaymentGateway
implementation, and everything else will work without changing any of the code.
Service Providers
Service providers serve the purpose of bootstrapping a Laravel application. This includes registering different bindings, event listeners, or routes, as well as configuring things like macros. All service providers must be registered within the config/app.php
config file.
Service providers include two methods: register
and boot
. The register
method should only be used to bind things into the service container. The boot
method is called after all the service providers have been registered, meaning its code has access to all the dependencies registered in any service provider. This method also supports type-hinting dependencies:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\SomeService;
class PaymentGatewayServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(
'App\Contracts\PaymentGateway',
'App\Services\StripePaymentGateway'
);
}
public function boot(SomeService $someService)
{
$someService->bootstrap();
}
}
Laravel bootstraps all of its components through service providers, and by default ships with an empty provider (AppServiceProvider
) so developers can use it for any additional custom bootstrapping. In larger applications, additional bootstrapping can be split between any number of different custom service providers.
Request Lifecycle
All HTTP requests are handled by the HTTP kernel. Internally, the HTTP kernel runs all required bootstrappers before the request is actually handled, which includes loading all of the service providers. Next, the request is passed to the router. The router runs all the applicable middleware—which we’ll discuss later—and then dispatches the request to the specified controller.
Summary
Understanding Laravel’s architecture is one of the keys to writing clean, flexible, and maintainable code. The developer should know where certain parts of the code belong and how different components of the framework work together. Their ability to answer questions on these topics should give interviewers confidence.

Laravel Fundamentals
With Laravel’s core architecture clear, let’s move on to Laravel’s extensive feature set, with which every seasoned developer should be well familiar. Each one of these modules is very likely to be used in the majority of Laravel-based applications, so interviewers should make sure candidates understand all of the concepts in this section.
Routing
Routing is a way of directing requests to controller methods based on the URL of a request. While Laravel’s router is a very powerful tool, we won’t go into details about all of its features. Instead, we’ll look at its basic functionalities and the things you should pay attention to while reviewing a candidate’s code.
Here’s an example of a simple route definition:
Route::get('posts')->uses('PostController@index');
That route will direct all /posts
GET requests to the index
method of PostController
.
By default, Laravel comes with web.php
and api.php
routes, located in the /routes
folder, and, as we mentioned before, a sensible set of middleware assigned to these routes via the RouteServiceProvider
.
The registered routes support all HTTP verbs:
Route::get(...);
Route::post(...);
Route::put(...);
Route::patch(...);
Route::delete(...);
Route::options(...);
It’s also possible to match multiple, or all verbs:
Route::match(['get', 'post'], ...);
Route::any(...);
Routes also accept a closure instead of a controller action:
Route::get('post', function() {
$posts = Post::all();
return view('postList')->with(['posts' => $posts]);
});
…however, this is considered bad practice in most cases and should not be used. It clutters the code and prevents the routes from being cached. Developers who are fairly familiar with Laravel will know this and avoid it.
When defining routes, you’ll often need access to parts of the route URI called parameters. Parameters are segments encased within {}
, can be optional ({param?}
), and are automatically sent to the controller method:
Route::get('user/{userId}/posts/{postId?}')->uses('UserPostController@show');
// ...
// UserPostController's show method
public function show($userId, $postId = null) { ... }
Another feature of the router is named routes:
Route::get('posts')->name('posts')->uses('PostController@index');
This makes it possible to easily generate URLs for routes in our views:
<a href="{{ route('dashboard') }}">Dashboard</a>
Or, for example, redirect the user after a successful login:
return redirect()->route('dashboard');
While the router offers many more features, this sums up the basic ones. Proficient use of these features should come effortlessly for an experienced Laravel developer.
Middleware
Middleware is a type of filtering layer that HTTP requests must pass through before entering the application. For example, authentication middleware can reject the request if the user is not logged in, role middleware can redirect the user based on their user role, etc.
By default, Laravel ships with a web
middleware group, which contains several useful middleware like session handling and cookie handling. It’s applied to all web requests by the RouteServiceProvider
.
Let’s see what a middleware class may look like:
namespace App\Http\Middleware;
use Closure;
class UserIsAdmin
{
public function handle($request, Closure $next)
{
if (! $request->user()->isAdmin()) {
abort(403);
}
return $next($request);
}
}
When applied to a route, this middleware will make sure non-admin users are denied access to that route (we’ll talk more about routing in a bit).
Middleware can be applied globally for every request, and per route as a single piece of middleware or a group of middleware. All middleware should be registered in the /app/Http/Kernel.php
class.
To run a piece of middleware for every HTTP request, add it to the list in the $middleware
property of the HTTP kernel class.
To assign middleware to a particular route, coders need to first add them to the list in the $routeMiddleware
property:
// App\Http\Kernel Class
protected $routeMiddleware = [
// Other middleware here...
'admin' => App\Http\Middleware\UserIsAdmin::class,
];
Now, the 'admin'
middleware can be assigned to a route:
Route::get('administration')->uses('AdminController@index')->middleware('admin');
When assigning multiple middleware to a route, developers may want to group it first under a single key in the $middlewareGroups
property:
protected $middlewareGroups = [
'activeAdmin' => [
App\Http\Middleware\UserIsActive::class,
App\Http\Middleware\UserIsAdmin::class
]
];
And then assign it to a route using that key:
Route::get('administration')->uses('AdminController@index')->middleware('activeAdmin');
Let’s look at our AdminController@index
method before applying the middleware:
// AdminController
public function index(Request $request)
{
if (! $request->user()->isAdmin()) {
abort(403);
}
return view('user.profile', ['user' => User::findOrFail($id)]);
}
Imagine if we had several methods in the AdminController
which only admin users should have access to. We’d have to check for that condition in every single method, which would unnecessarily clutter the code and make it harder to maintain. After applying the admin
middleware to our administration routes, we can clean up the code by getting rid of the condition check in the controller methods:
public function index(Request $request)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
When looking through the candidate’s code examples, this is a detail managers would do well to pay attention to.
Controllers
Serving as glue between the presentation and data layers, controllers are one of the key components of the MVC pattern. Pointing routes to controller methods allows you to easily organize request-handling logic within controller classes, which significantly improves code readability.
Controller methods can return several types of responses. Let’s list the most common ones:
// View:
return view('homepage')->with(['someVariable' => 'some value']);
// Response object:
return response($content)->header('Some-Header', 'Header-Value')->cookie('cookieName', 'cookieValue', $minutes);
// JSON:
return response()->json(['data' => $data]);
// File download:
return response()->download($filePath)->deleteFileAfterSend();
// Redirect
return redirect()->route('dashboard');
And a few other response types that may not be used as often:
// String
return 'Success';
// Array
return [1, 2, 3];
// Redirect to a controller action:
return redirect()->action('DashboardController@index');
// Redirect to external links:
return redirect()->away('https://www.toptal.com/');
Earlier, we talked about assigning middleware to routes. There’s also another way of assigning middleware: directly in the controller’s constructor. Specifying middleware in this way also supports attaching the middleware only to specific methods, or omitting it for specific methods. Let’s look at an example:
public function __construct()
{
$this->middleware('something');
$this->middleware('auth')->except('logout');
$this->middleware('guest')->only('logout');
}
One more important thing to address when it comes to controllers is dependency injection. When constructing a controller object, Laravel’s service container will inject type-hinted dependencies both in its constructor:
public function __construct(GuzzleHttp\Client $client)
{
$this->client = $client;
}
…and in the called methods, where the most common injected dependency will be the Request
instance:
public function update(Illuminate\Http\Request $request)
{
// ...
}
This covers the essential features of Laravel’s controllers. Understanding how controllers work and being able to use their features efficiently is a must for every dedicated Laravel developer.
Session
Sessions provide a way to retain information across multiple HTTP requests: user authentication state, form data that didn’t completely validate, etc. Laravel supports multiple session drivers:
file
cookie
database
memcached
redis
The default driver is file
, because that will work out of the box on basically any setup, and is adequate for most applications. But memcached
and redis
are preferred because they’re in-memory drivers, and therefore extremely performant.
There are two ways to use the session feature provided by Laravel: Firstly, if we have access to the Request
instance, we can call the session methods in the following way:
$request->session()->get('key');
Alternatively, we can use the session()
helper, which will work anywhere:
session('key');
Let’s look at the most commonly used methods for manipulating session data:
// Get value for 'key', or default value if it doesn't exist:
session('key', 'default');
// Get all data in the session
session()->all();
// Store value for 'key':
$request->session()->put('key', 'value');
session()->put('key', 'value');
session(['key' => 'value']);
// Delete key
session()->forget('key');
// Delete all session data:
session()->flush();
Since maintaining state across multiple requests is one of the key features of web development, it’s important the developer is familiar with sessions and differences between supported session drivers.
Validation
Validation is a way to check incoming request data against a set of rules. Laravel provides a simple, yet powerful, API to perform validation using numerous supported types of rules. As with many other Laravel topics, this one is extensive, so we’ll cover the basics that should be part of every good Laravel programmer’s toolset.
There are three different ways of using the validator. Firstly, if we have access to the Request
instance, we can call the validate()
method on it:
public function update(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|string|max:150',
'email' => 'required|email|max:150'
]);
// Use the validated data...
}
Secondly, we can type-hint a form request on a controller method. Form requests are custom request classes where developers can put validation logic, and they significantly reduce the clutter in controllers. This is the preferred method of performing validation in most cases:
public function update(UpdateUser $request)
{
// The request is automatically validated against the rules
// defined in the rules() method in the UpdateUser class
// Fetch the validated data
$validated = $request->validated();
}
This is what the rules()
method would look like:
public function rules(Request $request)
{
return [
'name' => 'required|string|max:150',
'email' => 'required|email|max:150'
];
}
Lastly, if the previous methods don’t give enough control over validation or how the validation error responses are generated by Laravel, we can manually create a Validator
instance:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class UserController extends Controller
{
public function update(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:150',
'email' => 'required|email|max:150'
]);
if ($validator->fails()) {
return redirect()
->back()
->withErrors($validator)
->withInput();
}
// Update user
}
}
The official list of validation rules can be good interview question fodder.
With that, we’ve covered the most important features of Laravel’s validator, and hopefully given you insight into what to look for in the candidate’s code samples.
Blade Templating
Blade is Laravel’s powerful, easy-to-use templating engine. While we could write an entire article just about Blade, we’re only going to go through the essential features every Laravel web developer should be familiar with.
Perhaps Blade’s most-used feature is its ability to extend layouts, use sections, and include sub-views. Let’s take a look at an example:
<!-- Main Layout -->
<html>
<head>
<title>Page</title>
</head>
<body>
@include('header')
@yield('content')
</body>
</html>
Here, we have a base layout which is located in /resources/views/layout.blade.php
. It includes a header subview, /resources/views/header.blade.php
, and it displays content from the content
section of the view which extends the layout. Here’s how we would write the /resources/page.blade.php
view containing that section:
@extends('layout')
@section('content')
<p>Lorem ipsum dolor sit amet.</p>
@endsection
Our header subview could look like this:
<header>
<h1>Lorem Page</h1>
</header>
Now, we just need to return the page.blade.php
view from our controller method:
public function show()
{
return view('page');
}
…and the rendered HTML will look like this:
<html>
<head>
<title>Page</title>
</head>
<body>
<header>
<h1>Lorem Page</h1>
</header>
<p>Lorem ipsum dolor sit amet.</p>
</body>
</html>
However, rather than just displaying static content, often, we’ll need to send data from controller methods to the Blade views where they’ll need to be displayed. This can be done very easily:
public function show()
{
return view('page')->with(['pageTitle' => 'Some Page']);
}
The $pageTitle
variable will now be accessible in our views. Let’s display it in the header:
<header>
<h1>{{ $pageTitle }}</h1>
</header>
Sometimes, we’ll also have collections of data which we’ll need to iterate over and display in the view. For example, let’s pull Post
s from the database:
public function show()
{
$posts = Post::all();
return view('postList')->with(['posts' => $posts]);
}
In resources/views/postList.blade.php
, Blade’s @foreach
and @if
statements make it a breeze to display only the active ones:
@extends('layout')
@section('content')
@foreach($posts as $post)
@if ($post->is_active)
<h2>{{ $post->title }}</h2>
{{ $post->content }}
@endif
@endforeach
@endsection
While there’s a lot more to the Blade templating engine, these are the fundamentals needed to write clean, scalable front-end code. In few cases, if ever, will Blade require any workaround solutions for a problem, so this is something to consider when evaluating the candidate’s code.
Authentication
Laravel comes with a complete and powerful, yet simple, authentication system implementation. By default, it uses the App\User
model—specifically its email
and password
properties—to authenticate users.
Interacting with the authentication system is done in one of two ways:
- Using the
Illuminate\Support\Facades\Auth
facade
- Using the
auth()
helper function
Both support the same methods, so using one or the other is just developer’s preference.
Let’s look at the most commonly used methods when working with authentication:
// Attempt to log in a user using credentials supplied in the request
if (auth()->attempt(['email' => $request->input('email'), 'password' => $request->input('password')])) {
// User is authenticated
}
// Log in an existing user instance
auth()->login($user);
// Log a user in using their ID
auth()->loginUsingId($id);
// Retrieve authenticated user
$user = auth()->user();
// Log the user out
auth()->logout();
// Determine if the current user is logged in...
if (auth()->check()) {
// User is logged in
}
// ...or not
if (auth()->guest()) {
// User is not logged in
}
This is a brief overview of how Laravel’s authentication system works. However, for most applications, this is really everything that’s needed to take advantage of its features. That, in turn, speeds up the development process by doing all the heavy lifting when it comes to everything authentication-related. Having a good grasp of the authentication system is one of the things that makes a great Laravel expert.
Authorization
Authorization is a way of allowing or preventing users to do certain actions—for example, creating a page or updating their username. There are two different approaches: Gates and Policies. Gates are closure-based and are mostly used for actions which are not related to any particular model, while policies are class-based with each policy being specific for a single model.
Let’s look at a couple of Gate
s, which will usually be defined in the AuthServiceProvider
:
public function boot()
{
$this->registerPolicies();
Gate::define('update-username', function ($user) {
return $user->role == 'admin';
});
Gate::define('delete-page', function ($user, $page) {
return $user->role == 'admin' && $page->canBeDeleted();
});
}
Gates always receive a user instance as the first argument, and can receive any number of additional arguments. To use Gate
s, we have two methods at our disposal—allows
and denies
:
if (Gate::allows('update-username')) {
// Current user can update their username...
}
if (Gate::denies('delete-page', $page)) {
// Current user cannot delete the page...
}
Unlike gates, each policy’s focus is a single model. For example, if you have an application which has categorized items, you’re likely going to have an Item
model and an ItemPolicy
that authorizes actions for that model.
Policies are registered in the policies
property of the AuthServiceProvider
, where all models are mapped to their corresponding policies. The easiest way to create a policy is via the artisan make:policy
command:
php artisan make:policy ItemPolicy
The policy should contain a corresponding method for each model action that needs to be authorized, like update
or delete
, and should return true
if the action is authorized, or false
if it’s not. Each method will receive the user instance as its first parameter, and optionally an instance of the model for which the action is being authorized:
namespace App\Policies;
use App\Item;
use App\User;
class ItemPolicy
{
// Every user can create an Item
public function create(User $user)
{
return true;
}
// Only item's owner can update the Item
public function update(User $user, Item $item)
{
return $item->user_id == $user->id;
}
// Only administrator can delete an item
public function delete(User $user, Item $item)
{
return $user->role == 'admin';
}
}
There are several ways we can use policies. The most common ones are calling the can
and cant
methods on the user instance:
if ($user->can('create', App\Item::class)) {
// ...
}
if ($user->cant('update', $item)) {
// ...
}
…and calling the authorize
method within a controller method on the controller instance:
public function delete(Request $request, $itemId)
{
$item = Item::find($itemId);
$this->authorize('delete', $item);
// User can delete the item
}
Gates and policies offer a clean and simple way to extract and group an app’s authorization logic. A proficient Laravel developer will use these methods to keep their code clean, readable, and scalable.
Collections
The Collection
class is a powerful wrapper for PHP arrays. It provides numerous convenient methods for working with arrays, such as map
, filter
, and contains
.
Creating a collection is very simple. We just call the collect()
helper function and pass an array as an argument:
$collection = collect(['some', 'values', 'here']);
Now, let’s take a look at some examples of how collections can make a developer’s life easier and the code cleaner:
collect([20, 13, 18, 22])->sort()->values()->toArray();
// [13, 18, 20, 22]
collect([
['name' => 'John', 'age' => 20],
['name' => 'Jane', 'age' => 30],
['name' => 'Mark', 'age' => 35],
['name' => 'Buzz', 'age' => 18]
])->filter(function($item) {
return $item['age'] >= 30;
})->toArray();
// [['name' => 'Jane', 'age' => 30], ['name' => 'Mark', 'age' => 35]]
collect([
['page' => 'Home'],
['page' => 'About Us']
])->map(function($item) {
$item['slug'] = Str::slug($item['page']);
return $item;
})->toArray();
// [['page' => 'Home', 'slug' => 'home'], ['page' => 'About Us', 'slug' => 'about-us']]
This is just a tiny fraction of what the Collection
class is capable of. In a proficient developer’s hands, collections will all but render PHP’s native functions for working with arrays obsolete—speeding up development and increasing code readability. Good Laravel developers will use collections wherever possible; anything less should be considered a red flag.
Artisan
Artisan is a command-line interface (CLI) that ships with Laravel. It provides numerous convenient commands for automating tedious, everyday tasks while writing an application, like generating controllers, models, and jobs; caching config, routes, and views; clearing the cache; creating migrations; and many more. (Running the php artisan list
command gives a full list of commands.)
While the built-in commands are sufficient in most cases, sometimes, a developer will want to be able to execute some arbitrary, custom code via an Artisan command. They can do so by creating a custom command, which will be placed in the /app/Console/Commands
folder:
php artisan make:command PruneUsers
Custom commands consist of $signature
and $description
properties, and the handle()
method. $signature
is used to run the command—for example, php artisan prune:users
—and it will be displayed alongside the $description
when artisan list
is called. When running the command, the handle()
method is called. This is where developers place the code to be run, and where they type-hint any dependencies:
namespace App\Console\Commands;
use App\User;
use App\Repositories\UserRepository;
use Illuminate\Console\Command;
class PruneUsers extends Command
{
protected $signature = 'prune:users';
protected $description = 'Prune inactive users';
public function __construct()
{
parent::__construct();
}
public function handle(UserRepository $userRepository)
{
$userRepository->inactiveUsers()->delete();
}
}
As we can see, Artisan commands can be very handy for automating certain tasks, and/or having quick and clean access to functionality both via the scheduler and the CLI. Being familiar with the Artisan CLI can increase productivity and help developers clean up their code.
Queues
Laravel queues enable apps to offload lengthy tasks (sending emails, generating reports, etc.) to the background for processing, which significantly increases app responsiveness. While this topic is very comprehensive, we’re going to give a brief overview of how queues work and how to use them.
Laravel supports several different queue drivers out of the box: database, Beanstalkd, SQS, and Redis. For local testing, there is also a sync
driver, which executes tasks immediately. Usually, developers will opt for one of these built-in connection options and configure several different queues (“lanes” that tasks can be dispatched into) on it—for example, one might have an emails
queue and a reportGeneration
queue. All of this is configured within the /config/queue.php
configuration file.
With the configuration all set, we can start sending jobs onto queues. A Job
is a dispatchable class, which just means it implements the Illuminate\Contracts\Queue\ShouldQueue
contract so it can be easily sent off to a queue by calling the dispatch()
method on it. Once in a queue, a job’s handle()
method will be called—this is where the task-processing logic is coded.
Jobs are created by running the make:job
Artisan command:
php artisan make:job GenerateReport
Let’s look at an example of a Job
class:
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class GenerateReport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $reportData;
public function __construct($reportData)
{
$this->reportData = $reportData;
}
public function handle()
{
// Generate Report logic...
}
}
Now, let’s dispatch it:
class ReportController extends Controller
{
public function generate(Request $request)
{
GenerateReport::dispatch($request->all())->onQueue('reportGeneration');
return response()->json(204);
}
}
Once the job has been dispatched onto the queue, it will get picked up by a queue worker. Queue workers are long-lived processes that hold the current Laravel application state in memory, and process jobs in queues. This means they will not pick up any code changes to your application, so you’ll need to restart queue workers each time you update the application. Queue workers are started and restarted with the following Artisan commands:
# start
php artisan queue:work
# restart
php artisan queue:restart
Queues can be a very powerful tool in a Laravel developer’s arsenal. Therefore, the candidate should be familiar with how and when queues should be used, and the basic difference between supported queue drivers.
Task Scheduling
Instead of having to ssh
into a server and define a separate cron
job for every recurring task, Laravel’s task scheduler allows developers to do so within PHP code using a fluent API. To make this work, all that’s needed is to set up a single cron
job on the server that runs the php artisan schedule:run
command every minute. Laravel will leverage this cron
job to assess the app’s defined tasks and run the ones that are due.
Scheduled tasks are defined in the schedule
method of the App\Console\Kernel
class. The scheduler can run Artisan commands, queued jobs, or any other arbitrary piece of code:
protected function schedule(Schedule $schedule)
{
// Run an artisan command once a week
$schedule->command('locations:update-images')->weekly();
// Queue a GenerateReport job once a day
$schedule->job(new GenerateReport)->daily();
// Execute arbitrary code every 30 minutes
$schedule->call(function () {
// Code...
})->everyThirtyMinutes();
}
The Laravel scheduler also supports various commands for defining the frequency at which the tasks will execute, like daily()
and weekly()
used in our examples.
Laravel’s task scheduler is a very convenient tool for “porting” cron
job definitions from the server to the app codebase, and therefore allowing developers to keep them within a project’s version control system (VCS)—e.g., Git, as well as making them more readable and elegant. Developers who know Laravel’s ins and outs will also know how to use the features offered by the task scheduler to their advantage.
Database
Interacting with a database in Laravel is very simple, using either the Illuminate\Support\Facades\DB
facade, or the Eloquent ORM. We’ll talk about Eloquent in a bit; for now, let’s look at how we can run raw SQL queries using the DB
facade:
// Select statement
DB::select('select * from posts where id > ?', [20]);
// Insert statement
DB::insert('insert into posts (title, slug, content) values (?, ?)', ['First Post', 'first-post', 'Lorem ipsum content']);
// Insert statement
DB::update('update posts set content = ? where slug = ?', ['Dolor sit amet content', 'first-post']);
// Delete statement
DB::delete('delete from posts where slug = ?', ['first-post']);
// General statement
DB::statement('ALTER TABLE posts CHANGE title post_title VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci');
Running raw SQL statements is sometimes required, but most times, there’s a better, more readable, API-based way of generating SQL queries: the query builder. Laravel’s query builder offers a convenient way of generating almost any query an application will ever need. Let’s look at the most commonly used methods:
// get() method returns a Collection of results
$posts = DB::table('posts')->get();
// where() method appends the WHERE clause
$posts = DB::table('posts')->where('id', '>', 20)->get();
// first() method returns the first result as an
// stdClass object that matches the criteria
$posts = DB::table('posts')->where('slug', 'some-post')->first();
// orderBy() method sorts the results
$posts = DB::orderBy('created_at', 'desc')->get();
// insert() method inserts a row into the database
DB::table('posts')->insert(
['title' => 'Some Post Title', 'slug' => 'some-post',
'content' => 'Lorem ipsum content']
);
// update() method updates a row in the database
DB::table('posts')->where('slug', 'some-post')
->update(['content' => 'Dolor sit amet content']);
// delete() method deletes rows from the database
DB::table('posts')->where('active', 0)->delete();
Here’s a few more examples of what can be achieved using the query builder:
// Select all posts which are either active,
// or created by users with id 1, 2 or 3
$posts = DB::table('posts')->where('is_active', 1)
->orWhereIn('user_id', [1, 2, 3])->get();
// Skip 20 posts, then select the next 10
$posts = DB::table('posts')->skip(20)->take(10)->get();
// Join posts with users, and select all columns from
// 'posts' table, and 'name' from 'users' table
$posts = DB::table('posts')
->join('users', 'posts.user_id', '=', 'users.id')
->select('posts.*', 'users.name')->get();
Another convenient feature provided by the DB
facade are transactions. A transaction ensures that either all of the queries within it are executed, or none are. There are two ways to use transactions: closure-based and manually:
// Closure based
DB::transaction(function () {
DB::table('users')->where('id', 5)->update(['active' => 1]);
DB::table('users')->where('active', 0)->delete();
});
// Manual
try {
DB::beginTransaction();
DB::table('users')->where('id', 5)->update(['active' => 1]);
DB::table('users')->where('active', 0)->delete();
DB::commit();
} catch (\Exception $e) {
DB::rollback();
}
The closure-based approach is generally simpler to use because it automatically handles committing its transaction, or rolling it back in case of an exception. But the manual approach can offer more control in situations where that’s needed.
One last, but definitely not least, feature to mention is database migrations. Migrations are classes which allow for version-controlling database changes—everything from creating tables and columns, deleting them, and updating them. They consist of up
and down
methods. The up
method is run when first changing the database in some way (creating a table, column, etc.), and the down
method is used for reverting those changes.
Migration classes are generated by running the make:migration
Artisan command:
php artisan make:migration create_posts_table
Artisan places the migration files in the /database/migrations
folder. A migration file will include various column types (string, text…) and modifiers (index, nullable…):
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title')->index();
$table->string('subtitle')->nullable();
$table->string('slug')->index();
$table->text('content');
$table->timestamps();
});
}
public function down()
{
Schema::drop('posts');
}
}
Migrating the database is done via the migrate
Artisan command:
php artisan migrate
This will run the up
method in the migration classes. Rolling back the migrations is done by running:
php artisan migrate:rollback
This will run the down
method in the migration classes.
While options for interaction with databases are plentiful in Laravel, the API is fairly easy to remember, which makes for an efficient developer experience. Having a deep understanding of how the database API works is very important for Laravel application development, as it affects both productivity and code quality.
Eloquent: Laravel’s Object-relational Mapper (ORM)
Eloquent is likely the most complex component of the Laravel framework, but its basic functionalities are the foundation for using Laravel proficiently. Each Eloquent model maps directly to a database table, and each Model
object maps to a database row in that table. This, and the fact that it uses Laravel’s query builder underneath to generate SQL queries makes it an invaluable tool for interacting with the database in an object-oriented way.
All Eloquent models must extend the Illuminate\Database\Eloquent\Model
class, and the easiest way to create them is by using the make:model
Artisan command:
php artisan make:model Post
This will generate the following class in the app
folder:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
//
}
By default, the table name the model corresponds to will be auto-guessed by Eloquent—for our particular example, a posts
table. If developers wish to define a custom table name, they can use the protected $table
property on the model.
Now that we have our model and know which database table it will use, let’s look at the most common operations we can perform, using the same fluent API as we did with the query builder:
// Return a collection of Post objects
$posts = Post::all();
$posts = Post::where('active', 1)->get();
$posts = Post::where('user_id', 5)->orderBy('created_at', 'desc')->get();
// Return a single Post object
$post = Post::find(5);
$post = Post::where('slug', 'some-post')->first();
// Create a Post object
$post = Post::create(['title' => 'Some Post', 'slug' => 'some-post', 'content' => 'Lorem ipsum content']);
// Update the Post object
Post::where('slug', 'some-post')->update(['content' => 'Dolor sit amet content']);
// Alternatively
$post = Post::where('slug', 'some-post')->first();
$post->content = 'Dolor sit amet content';
$post->save();
// Delete the matching Post objects
Post::where('active', 0)->delete();
// Alternatively, delete a Post object
$post = Post::where('slug', 'some-post')->first();
$post->delete();
Another powerful feature of the Eloquent ORM is managing table relationships. For example, a Category
model may have many Post
s, and a Post
may belong to a User
. Eloquent provides a fluent API for managing several types of relationships:
- One to one
- One to many
- Many to many
- Has one through
- Has many through
- One to one (polymorphic)
- One to many (polymorphic)
- Many to many (polymorphic)
We’re going to take a look at the most commonly used relationships:
-
One to many (a
Category
has many Post
s)
- Its inverse, belongs to (each
Post
belongs to a single User
)
-
Many to many (a
Post
has many Tag
s, and a Tag
can belong to many Post
s)
Relationships are defined as methods that return a relationship instance. Let’s define the one to many Post
s relationship on our Category
model:
class Category extends Model
{
public function posts()
{
return $this->hasMany('App\Post');
}
}
For this relationship to work, we must have a category_id
column in our posts
table, which is automatically assumed by Laravel by concatenating category
(snake-cased name of the Category
model) and adding _id
to it. Now we can call the relationship:
$categoryPosts = $category->posts()->get();
This will return a collection of Post
s that belong to the given Category
. Alternatively, we can achieve the same result by calling posts
as a dynamic property instead of the posts()
method. Eloquent is smart enough to map the posts
dynamic property to the relationship method and return the desired Post
s collection:
$categoryPosts = $category->posts;
We can also apply filters, same as we did before:
$activePosts = $category->posts()->where('active', 1)->orderBy('created_at', 'desc')->get();
If we want to find out which User
owns a certain Post
, we can define inverse of the hasMany
relationship (belongsTo
) on our Post
model:
class Post extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
Let’s call it:
$user = $post->user;
Once again, notice we’re not calling the user()
method, but rather the user
dynamic property.
The many to many relationship requires an additional, intermediary (pivot) table. If we want our Post
s to have many Tag
s, and the Tag
s to belong to many Post
s, then our intermediary table will need to be named post_tag
(snake-cased models ordered alphabetically) and have post_id
and tag_id
columns.
Now we can define relationships on the models:
class Post extends Model
{
public function tags()
{
return $this->belongsToMany('App\Tag');
}
}
class Tag extends Model
{
public function posts()
{
return $this->belongsToMany('App\Post');
}
}
This allows us to find all the Tag
s for a given Post
:
$postTags = $post->tags()->get();
And all the Post
s a given Tag
belongs to:
$tagPosts = $tag->posts()->get();
Eloquent also provides a way of optimizing database queries using eager loading. Eager loading solves the “N + 1 problem” when iterating over a collection of Model
objects and querying their relationships. For example, let’s iterate over a category’s posts and display the name of each post’s user:
$category = Category::find(1);
foreach ($category->posts as $post) {
echo $post->user->name;
}
If our category has 10 posts, this code segment will generate 12 queries. Let’s see what happens:
Category::find(1);
This will query the database for a Category
with an ID of 1.
foreach ($category->posts ...
This will load the category’s posts into the Category
object. Now comes the problematic part:
$post->user...
We have 10 posts, and each post will query the database to fetch the user who owns the post, so we can display their name. To fix this, we can eager load all the users for all of our category’s posts:
$category = Category::with('posts.user')->where('id', 1)->first();
By calling the with('posts.user')
method, we can tell Eloquent that we want it to find all the posts belonging to the category with ID 1, then find all of their users in a single database query. This will result in a total of only three queries, regardless of the number of posts the category has:
- One query for fetching the category
- One query for fetching all of its posts
- One query for fetching all of users for these posts
This sums up the basics of the Eloquent ORM. While Eloquent can do much more than what we’ve discussed here, this should be enough to evaluate how far the developer’s knowledge reaches. Understanding Eloquent is a must for every expert Laravel developer, so including Eloquent-related questions in the interview process will go a long way.
Debugging
Laravel’s simplicity can be a double-edged sword. It can fool novice developers into thinking that writing code will always be a smooth experience, and nothing can ever go wrong. But that’s not how software development works: Once they hit a wall in the form of a deceiving bug, they’ll get lost and won’t know how to move forward. An error 500 screen will be presented to them, and they won’t know what the next step is toward the solution.
One of the most valuable traits of an experienced developer is being proficient in debugging. When it comes to Laravel, there are several methods to debug an application.
The first thing the developer should be aware of is the debug
option in the config/app.php
config file: When set to true
, after hitting an exception in code, the exception details screen will be shown in the browser, instead of a blank error 500 page. This will, in most cases, point us to the exact line of code where the exception happened and provide a lot of helpful information about the exception. All of this information will also be written to the storage/logs/laravel.log
log file, regardless of the value of the debug
option.
Another great way to debug is using the Laravel Debugbar library. Apart from displaying all of the exception-related data, Debugbar will also provide information about request data, session data, and executed database queries, which can speed up debugging considerably. Other debugging libraries worth mentioning are Laravel Telescope and the Clockwork browser extension.
How to Hire the Best Laravel Developers: Experience with Best Practices Is Key
Finding and hiring a great Laravel freelancer can definitely be a tall order. The simplicity of the Laravel framework and its excellent, easy-to-understand documentation make it easy for almost any developer to pick it up quickly, regardless of their experience. This can make it difficult to weed out novice developers and find the right fit for your team.
Our Laravel hiring guide provides enough knowledge and understanding of how Laravel web development works to help recognize skilled developers in the USA or abroad and make the right choice for their business needs. However, not knowing everything mentioned in this guide off the top of their head does not necessarily mean the developer is not good at what they do. And vice-versa: Memorizing all of Laravel’s documentation without any practical years of experience does not make a developer great.
With this guide on how to hire dedicated Laravel developers, interviewers can readily craft the relevant interview questions they need, evaluate their candidates’ knowledge, and make the final call within their normal hiring models. Best of luck!