GSD
19+ Laravel Best Practices for Developers in 2024
Posted by Goodness Woke on May 8, 2024
The necessity for clean, testable, scalable, and maintainable code, particularly in Laravel, has consistently pushed for adopting well-established best practices. However, due to the flexibility of PHP's Laravel, deviating from the well-established Laravel best practices can be tempting. This is problematic because it may result in a highly unmaintainable code and reduce the rate at which bugs are discovered.
Now, let's explore what Laravel is and discuss some of the most important Laravel best practices for building applications.
Table of contents
What is Laravel?
Laravel is a popular and respected open-source, free PHP programming language framework that is aimed at making development processes appealing without having to sacrifice quality. It uses an MVC (Model View Controller) pattern which makes the language more relatable and adaptable. It can also be regarded as a web application framework with expressive and elegant syntax that attempts to take the pain out of development by easing common tasks used in web projects and applications.
For history's sake, you should know that Laravel was built in place of the CodeIgniter framework as a modern replacement because the CodeIgniter framework missing important core features such as built-in support for user authentication and authorization. Since its initial launch in 2011, Laravel has implemented built-in support for localization, views, dealing with sessions, routing the request to the specific controller, and other amazing features.
PHP problems that Laravel fixes
Over the years Laravel has come to solve various PHP problems such as the following:
-
It ensures that unauthorized users have no illegal access to paid or secured resources and configures almost everything out of the box.
-
In Laravel, error handling is pre-configured, and errors are logged in an
AppExceptionsHandler
class; however, you can customize this behavior using thewithExceptions
method inbootstrap/app.php
. -
Laravel has several tools to address PHP’s application development and deployment inefficiencies. These include deployment tools like Laravel Forge and Laravel Vapor, starter kit tools like Laravel Breeze, and many others.
-
Equally, a built-in command scheduler allows you to configure your schedule within Laravel, solving the problem of implementing a task scheduling system.
-
Repetitive programming tasks can often be overwhelming, but the PHP Artisan command line used when interacting with Laravel completes numerous programming tasks, so developers don’t have to do them manually.
-
Due to its large user base and active community, technical vulnerabilities are often quickly spotted and taken care of by the Laravel maintenance team.
-
Laravel also has out-of-the-box testing tools like Pest and PHPUnit and functionalities to enable expressive testing. It also supports executing automated testing sessions that are more precise than manual ones.
-
Managing notifications and events solely through PHP can be extremely complex. Laravel addresses this by providing a dedicated notification and event system.
-
When you’d like to specify your user’s intention for the system to understand where the user is navigating, Laravel allows for configuring URL routing for easy route configuration.
Structure of Laravel
As previously stated, Laravel follows the MVC architecture, meaning that interaction occurs on the View, and the Controller processes the request, retrieving data via the Model and returning the expected response to the user via the View.
So, when a client sends an HTTP request to the Laravel server, the server forwards the request to the Router, which routes it to the appropriate Controller. If Middlewares are defined, the request passes through them before reaching the Controller. The Controller calls the necessary Models and Services needed to process the request, and finally, the Controller returns an HTTP response as the View.
Core features of Laravel
The Laravel framework has incredible features that make up its structures, such as:
-
Laravel Eloquent ORM (Object Relational Mapping): Eloquent ORM is an Object Relational Mapping package for the Laravel framework, making it easier to interact with the database. It constitutes the Model component of the Laravel structure, enabling the representation of relational database table records as objects and database tables as Models. As a result, there is little or no need to interact with the database using SQL queries. It's faster to interact with the database using ORMs. Define the tables and their relationships, and the Eloquent Model will handle the rest. The Eloquent Model can handle multiple database synchronization, update, delete, insert, and many others.
-
Security standards: The framework’s structure provides an incredible level of protection, such as security-related CSRF tokens, which ensure that the authenticated user is the person making the request to the application. It also secures private credentials using the Hashing Algorithms. Laravel also stores passwords as hashed and salted hashes rather than plain text.
-
Template Engine: The framework comes with the built-in Blade Template Engine. This engine rendered .blade.php files containing HTML templates and some blade directives.
-
Route handling: It handles routes in an extremely simple way and can be used to build a single interface between your data and client, thereby making it a great candidate for RESTful APIs. In simpler terms, it allows requests to a specific URL like “login,” which is useful for social networks and sites.
-
Authentication: Laravel has built-in authentication and session services, which can be used via the Auth and Session facades. It offers cookie-based authentication primarily for requests coming from the browser.
-
Session/Cache handling: Laravel collects requests and stores information about the user in drives like File, Redis, Memcached, etc. Users can easily retrieve stored items because they are stored in fast and easily accessed data stores like those mentioned above. Laravel provides mechanisms to compile assets with all the intricacies of storage and file management. This cache handling increases an application's speed.
-
Unit testing: Just as the name implies, unit testing performs tests on single models and controllers to uncover issues. According to Laravel documentation, support for testing with PHPUnit is included out of the box, and a
phpunit.xml
file is already set up for your application whenever you get started.
Apart from the features listed above, Laravel has other additional features such as Database Migrations, Task Scheduling, Job Queues, and Request Validation.
What is Laravel best used for?
Laravel, as a PHP framework, is one of the best tools for building a variety of web applications, including:
-
Custom web applications and content-heavy sites: Laravel is an excellent choice for building applications that handle a lot of content, like blogs (see how you can build a blog with Laravel here). It offers improved performance and a unique way of managing traffic through its caching system, which stores frequently accessed data and pages to reduce fetching and compilation overhead. This makes the application faster and more efficient.
-
Multilingual applications: Laravel enables building multilingual apps to provide a seamless user experience regardless of language. Its localization feature allows for easy management of translations. This enables support for multiple languages, making your application more accessible.
-
Social networking sites: It’s also a great option for building Social network sites because it has a middleware feature and tight security. This feature, which is available by default in Laravel, makes it the best tool for allowing only genuine users access to a specific site.
-
E-Commerce sites: Laravel's robust authentication system makes it a superb choice for building e-commerce websites. When customers log in to purchase an item, Laravel ensures they can safely perform transactions. Additionally, its authentication system provides an easy way to manage user access, providing customers with a secure and personalized experience.
-
API development: Laravel's strong support for API development makes it a prime choice for developing scalable and maintainable APIs. Its built-in features, such as routing, versioning, error handling, and security, work together to facilitate API development, allowing developers to focus on creating robust and efficient APIs. Additionally, with Laravel’s middleware features, requests and responses can be modified or transformed to the required format.
-
Real-Time Applications: Laravel makes it easy to build real-time applications, thanks to tools like Laravel Echo. This two-way communication between the server and client is crucial for features like live updates, chat systems, and collaborative tools. The
PresenceChannel
, a Laravel Echo feature, also allows tracking of user presence and activity in real-time, making it easier to build collaborative features.
Laravel best practices for developers in 2024
Alright, it's time for the main event! This section will review the essential best practices when working with Laravel, covering Laravel API best practices, Laravel project structure best practices, and Laravel controller best practices. By following these guidelines, you'll be able to write clean, efficient, and maintainable code that meets the standards.
Always use the most stable release
The most recent version of Laravel, Laravel 11.x, was released on March 12th, 2024. This new release includes some important updates, which can be found here. These updates include:
Opt-in API and broadcast routing: Before version 11, when you created a new Laravel project, it set up the API and broadcast routing. But for the Laravel 11, these routings are optional and can be added using the artisan command:
php artisan install:api
php artisan install:broadcasting
Make sure to follow the new process and steps outlined in the release documentation.
Simplified base controller class: The AuthorizesRequests
and ValidatesRequests
traits have been removed. If you need them, you’ll have to manually add them. Also, the base Controller class no longer extends the Laravel internal Controller class.
Keep business logic in the service class
To ensure clean code, users must implement abstractions. While controllers typically manage business logic, there are instances where this logic may need to be reused by other controllers. In such cases, it's advisable to encapsulate this logic within separate service classes. This approach helps maintain code cleanliness by avoiding redundancy and promoting reusability.
Here's an example of a bad approach where the business logic is tightly coupled to the controller and is not reusable:
namespace App\Http\Controllers;
...
class OrderController extends Controller
{
public function placeOrder(Request $request)
{
$data = $request->all();
$user = User::find($data['user_id']);
$order = new Order();
$order->user_id = $user->id;
$order->total = $data['total'] + ( $data['total'] * 0.5);
$order->status = 'pending';
$order->save();
// ...
}
. . .
}
This snippet creates an order record using the user data and the data from the request. It also calculates the total cost of the order.
To do this better, you’ll need to refactor the code to look like this:
namespace App\Services;
...
class OrderService
{
public function createOrder(array $orderData)
{
// Encapsulated business logic
$user = User::find($orderData['user_id']);
$order = new Order();
$order->user_id = $user->id;
$order->total = $data['total'] + ( $data['total'] * 0.5);
$order->status = 'pending';
$order->save();
return $order;
}
}
Then, inject the service into the controller as demonstrated below:
...
use App\Services\OrderService;
class OrderController extends Controller
{
private $orderService;
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
public function placeOrder(Request $request)
{
$orderData = $request->all();
$order = $this->orderService->createOrder($orderData);
// ...
}
}
Use helper functions
Laravel comes with several helper functions and methods. Instead of reinventing the wheel, using the existing methods is preferable, as it will keep the code base concise and prevent repetition. A typical example is when you want to generate a random string. Instead of creating a new function that does that, you can use the Illuminate/Support/Str
by doing the following:
$slug = Str::random(24);
These helpers are available anywhere within the application.
Obey the single responsibility principle (SRP)
A class and method should have just one responsibility. This makes software implementation easy and intercepts any unforeseen side effects that arise due to changes that occur in the future.
For instance, instead of having a method that returns the transaction data by performing several operations, as demonstrated by the snippet below:
public function getTransactionAttribute()
{
if ($transaction && ($transaction->type == 'withdrawal') && $transaction->isVerified()) {
return ['reference'=>$this->transaction->reference, 'status'=>'verified'];
} else {
return ['link'=>$this->transaction->paymentLink, 'status'=>'not verified'];
}
}
To improve readability, spread the actions across methods like this:
public function getTransactionAttribute(): bool
{
return $this->isVerified() ? $this->getReference() : $this->getPaymentLink();
}
public function isVerified(): bool
{
return $this->transaction && ($transaction->type == 'withdrawal') && $this->transaction->isVerified();
}
public function getReference(): string
{
return ['reference'=>$this->transaction->reference, 'status'=>'verified'];
}
public function getPaymentLink(): string
{
return ['link'=>$this->transaction->paymentLink, 'status'=>'not verified'];
}
Use the Artisan CLI tool
The artisan CLI tool has a lot of commands that can help scaffold a setup you want. For instance, instead of manually writing your migration files and creating the model, use the command:
php artisan make:model Branding -m
There are several other artisan commands you can use, which include:
-
The create Request command.
php artisan make:request LoginRequest
-
The optimization command, which can be used to clear cache.
php artisan optimize:clear
-
The command to run migrations
php artisan migrate
-
Also, the command to run the tests
php artisan test
You can check out Laravel documentation for even more details.
Carry out validation in request classes
Validation is crucial when handling user input in your Laravel application to ensure data consistency and prevent errors. Inline validation rules in controller methods can become cumbersome and repetitive. To address this, Laravel provides FormRequest classes that allow you to define validation rules in a centralized and reusable way
Previously, you might have written validation rules directly in your Controller method like this:
public function store(Request $request)
{
$request->validate([
'slug' => 'required',
'title' => 'required|unique:posts|max:255',
...
]);
}
Instead, create a dedicated FormRequest class using the artisan CLI tool:
php artisan make:request StoreRequest
In this class, define your validation rules:
class StoreRequest extends FormRequest
{
...
public function rules(): array
{
return [
'slug' => 'required',
'title' => 'required|unique:posts|max:255',
...
];
}
}
Then, update your controller method to use the FormRequest
class that you created:
public function store(StoreRequest $request)
{
...
}
By using FormRequest
classes, you can decouple validation logic from your controller and reuse these rules across your application. This approach promotes cleaner code, reduces duplication, and makes maintenance easier.
Timeout HTTP request
One of the best practices Laravel has to offer is utilizing Timeouts. By using the timeout method, you can avoid errors when sending HTTP requests from your controller. Laravel defaults to timeout requests to its server after 30 seconds. If there is a delay by the HTTP request and no error message, your app could remain stuck indefinitely.
Here is an example of how to timeout an HTTP request after 120 seconds.
public function store(StoreRequest $request)
{
....
$response = Http::timeout(120)->get(...);
....
}
Take advantage of mass assignments
In Laravel, mass assignments are useful to avoid scenarios where users might unintentionally alter sensitive data, such as passwords or admin status. For instance, suppose you have a product that you want to save in the Product
model. Instead of using the following code:
$product = new Product;
$product->name = $request->name;
$product->price = $request->price;
$product->save();
You can use the create
static method from the model class and pass in the validated request array like so;
Product::create($request->validated());
This method handles mass assignment.
Chunk data for heavy data tasks
When processing a large amount of data from the database, instead of fetching the data and running a loop through the large data like this:
$products = Product::all() /* returns thousands of data */
foreach ($products as $product) {
...
}
Use the chunk
method by specifying a fixed amount you want to process at a time and the closure for processing. Here is an example:
Product::chunk(200, function ($products) {
foreach ($products as $product) {
// Perform some action on the product
}
});
Do not use environment variables (.env) directly in your code
In Laravel, use env(..) in config files to define values and config(..)
in application codes to retrieve them. This ensures compatibility with the config caching system.
For example, if you have to use an external service that requires API keys, it must be secure. After you've added this key to your key store (.env file) to secure it, instead of retrieving the keys directly, just like this:
$chat_gpt_key = env("CHAT_GPT_API_KEY");
It's best to add it to your config/api.php using the following:
[
...
'chat_gpt_key' => env("CHAT_GPT_API_KEY"),
]
Then, when you need to use the key within any section of your project, just fetch it using the config
function provided by Laravel.
$chat_gpt_key = config('api.chat_gpt_key');
This gives you more control over the environment variable, allowing you to set a default value even when the environment variable doesn't exist in your key store (.env file). Also, using the config method has performance benefits, as Laravel caches the configurations present in the configuration files.
Use Eloquent instead of Query Builder and raw SQL queries
Eloquent allows you to write readable and maintainable code. It comes with useful tools like soft deletes, events, scopes, etc., and will enable you to set conditions for your database table queries to ensure your table's data stays correct. It simplifies managing relationships between models.
You don't need to write complex SQL code when you have Eloquent in Laravel. For instance, if you want to fetch a list of products and their categories, filter by availability and sort them by the most recent. Instead of using the SQL query like this:
SELECT *
FROM `products`
WHERE EXISTS (SELECT *
FROM `categories`
WHERE `products`.`category_id` = `categories`.`id`
AND `categories`.`deleted_at` IS NULL)
AND `is_available` = '1'
ORDER BY `created_at` DESC
You simply use this:
Product::has('category')->isAvailable()->latest()->get();
Both code snippets are retrieving products that have a related category, are marked as available, and are sorted in descending order by creation time. While the first snippet uses raw SQL, the second uses the Eloquent method chaining to apply these conditions, making the intent of the code more explicit and easier to understand.
Maintain Laravel naming conventions
Follow the PSR Standards as stated here, and also use the naming conventions accepted by the Laravel community which will help in organizing your files. Using consistent naming conventions is important because inconsistencies can cause confusion which will eventually lead to error. A tabular guideline is provided below.
Avoid using documentation comment blocks (DocBlocks)
Reduce the need for additional documentation by striving to write clear and understandable code. This approach makes your code cleaner, readable, and more maintainable.
A well-named method or class can often serve as its own documentation, eliminating the need for DocBlocks. For example:
public function calculateTotalOrderCost(Order $order): float
{
// Calculate and return the total cost
}
In this example, the method name calculateTotalOrderCost
clearly indicates its purpose, making additional documentation unnecessary.
Use standard Laravel tools
Laravel offers a range of tools to enhance development, including:
-
Sanctum: Simplifies authentication and authorization
-
Socialite: Streamlines social media authentication
-
Jetstream: Accelerates application scaffolding and provides pre-built UI components
-
Mix: Optimizes asset compilation and management
These tools standardize your code, make it maintainable, and are regularly updated with community support.
Use shorter and more readable syntax in your Laravel code
Using shorter syntax makes your Laravel code readable and consistent and reduces cognitive load. For instance, if you want to get the particular session data from the request session, instead of using the following:
$request->session()->get('order')
or
Session::get('order')
You can simply use this:
session('order')
Implement the down() Migration method
Most developers often overlook implementing the down()
method in their migration file. This neglect can have significant consequences, particularly in successfully executing rollbacks. Therefore, it is a highly recommended Laravel best practice to always implement the down()
method for every up()
method in your migration file.
For instance, if you have an orders table migration file that creates a new column, fee
:
return new class extends Migration
{
public function up()
{
Schema::table('orders', function (Blueprint $table) {
$table->unsignedInteger('fee')->default(0);
});
}
...
};
You’ll need to implement the down() method, which negates the creation of the column fee.
return new class extends Migration
{
public function up()
{
. . .
}
public function down()
{
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn('fee');
});
}
}
Additional Laravel best practices to keep in mind
Apart from the Laravel best practices discussed above, there are additional practices that improve your codebase readability and maintainability. These practices include:
Laravel API best practices
-
Use middleware to implement authorization and permission for your API endpoints. This gives your codebase centralized control and improves scalability.
-
Adopt API versioning so that you can maintain various versions of your endpoints and introduce updates without interfering with existing integrations.
-
Model serialization should be done using Eloquent's API resources, which comply with Laravel API response best practices. This provides an effective and maintainable approach to managing API response, giving you more control and a reusable solution.
Laravel project structure best practices
-
Adhere to the Laravel Directory Structure. This allows you to find and debug your application easily.
-
Implement modularization by breaking down large applications into smaller, manageable modules to improve maintainability and scalability.
Laravel controller best practices
-
Use Laravel's resource controllers to define RESTful routes and methods for CRUD operations, promoting clean and predictable API endpoints when necessary.
-
Inject dependencies into your controllers instead of directly instantiating them. This promotes decoupling, making your code more testable and flexible.
Laravel cache best practice
-
Employ cache tags and cache invalidation strategies to manage cached data effectively and prevent stale or outdated information.
-
One of Laravel's REST API best practices involves caching regularly accessed data, which doesn't change often. This reduces the database data retrieval overhead.
Laravel testing best practices
-
Implement integration tests to verify interactions between different parts of your application, including database operations, API requests, and external service integrations.
-
Structure your tests using the Arrange-Act-Assert (AAA) pattern. This promotes readability and maintainability.
The future of Laravel
Laravel, one of the most popular PHP frameworks and already on its 11th version, will likely continue advancing and growing for the foreseeable future. As we look beyond version 11, here are some exciting possibilities that might shape the future of Laravel:
-
Deeper embrace of cloud-native technologies such as serverless architecture.
-
Laravel might openly embrace GraphQL, allowing developers to build their GraphQL APIs.
-
Major improvements to existing tools in the Laravel ecosystem, as well as the introduction of new tools to enhance developer experience, and so on…
Final thoughts
Following the Laravel best practices outlined in this article will make your next project much cleaner and easier to maintain. The latest version of Laravel includes some new features that can improve your development experience. You can also integrate your Laravel application with ButterCMS to achieve the best results.
Ready to put these Laravel best practices into action? Check out our guide on building a Laravel blog with ButterCMS to see how you can create a powerful, content-driven application in no time.
Post updated May 2024 by Obisike Treasure.
ButterCMS is the #1 rated Headless CMS
Related articles
Don’t miss a single post
Get our latest articles, stay updated!
Goodness is both a professional and enthusiastic writer and software developer.