Where do I put custom code in Laravel
Categories:
Where to Put Custom Code in Laravel: A Comprehensive Guide

Discover the best practices and common patterns for organizing your custom PHP code within a Laravel application, ensuring maintainability, scalability, and adherence to framework conventions.
Laravel's flexible architecture provides numerous places to integrate custom code, from simple helper functions to complex service layers. The key is to choose the right location for the right purpose, balancing accessibility with separation of concerns. This guide will walk you through the most common and recommended patterns for organizing your custom PHP logic within a Laravel project.
Understanding Laravel's Core Structure
Before diving into custom code placement, it's crucial to understand Laravel's default directory structure. The app/
directory is the heart of your application's business logic, containing models, controllers, providers, and more. Other directories like config/
, database/
, resources/
, and routes/
serve specific purposes. Adhering to these conventions helps maintain consistency and makes your application easier to understand for other developers.
flowchart TD A[Laravel Application] --> B{app/ Directory} B --> C[Models] B --> D[Controllers] B --> E[Providers] B --> F[Exceptions] B --> G[Http] B --> H[Console] B --> I[Custom Code Locations] A --> J[config/] A --> K[database/] A --> L[routes/] A --> M[resources/] I --> N[Services] I --> O[Repositories] I --> P[Actions] I --> Q[Helpers] I --> R[Traits]
Overview of Laravel's core directory structure and potential custom code locations.
Common Custom Code Locations
Laravel doesn't explicitly dictate where every piece of custom code must reside, offering flexibility. However, several patterns have emerged as best practices for different types of logic. The goal is to keep your controllers lean and delegate complex operations to dedicated classes.
1. Services and Business Logic
For complex business logic that doesn't directly fit into a model or controller, a dedicated Services
directory is a popular choice. These classes encapsulate specific functionalities, making them reusable and testable. You can create a Services
directory directly under app/
.
// app/Services/OrderProcessor.php
namespace App\Services;
use App\Models\Order;
use App\Models\Product;
class OrderProcessor
{
public function processOrder(array $items, int $userId): Order
{
// Complex logic for order creation, stock management, etc.
$order = new Order();
$order->user_id = $userId;
$order->status = 'pending';
$order->save();
foreach ($items as $item) {
$product = Product::findOrFail($item['product_id']);
// Attach products to order, update stock, etc.
}
return $order;
}
}
Example of a Service class for processing orders.
2. Repositories for Data Abstraction
The Repository pattern provides an abstraction layer between your application's business logic and the data persistence layer (e.g., Eloquent). This makes your application less dependent on a specific ORM or database, improving testability and allowing easier swapping of data sources. You might create an app/Repositories
directory.
// app/Repositories/UserRepository.php
namespace App\Repositories;
use App\Models\User;
class UserRepository
{
public function getAllUsers()
{
return User::all();
}
public function findUserById(int $id)
{
return User::findOrFail($id);
}
public function createUser(array $data)
{
return User::create($data);
}
}
A simple User Repository for abstracting data access.
3. Actions/Jobs for Single-Responsibility Tasks
For single, self-contained tasks that might be dispatched synchronously or asynchronously, Laravel's Jobs
(often referred to as Actions when they're not queued) are excellent. They promote the Single Responsibility Principle. You can place these in app/Jobs
(for queueable tasks) or create app/Actions
for non-queueable, reusable actions.
// app/Actions/SendWelcomeEmail.php
namespace App\Actions;
use App\Models\User;
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail
{
public function execute(User $user)
{
Mail::to($user->email)->send(new WelcomeMail($user));
}
}
An Action class for sending a welcome email.
4. Traits for Reusable Logic
Traits allow you to reuse methods across different classes without using inheritance. They are perfect for sharing common functionalities like logging, status updates, or specific calculations that apply to multiple models or controllers. Place them in app/Traits
.
// app/Traits/HasUuid.php
namespace App\Traits;
use Illuminate\Support\Str;
trait HasUuid
{
protected static function bootHasUuid()
{
static::creating(function ($model) {
if (empty($model->{$model->getKeyName()})) {
$model->{$model->getKeyName()} = (string) Str::uuid();
}
});
}
public function getIncrementing()
{
return false;
}
public function getKeyType()
{
return 'string';
}
}
A trait to automatically assign UUIDs to models.
5. Helpers for Global Functions
While generally discouraged for complex logic, simple, globally accessible helper functions can be useful for tasks like formatting, string manipulation, or common calculations. You can create a app/Helpers.php
file and include it in your composer.json
for autoloading.
// app/Helpers.php
if (! function_exists('format_price')) {
function format_price($amount, $currency = 'USD')
{
return number_format($amount, 2) . ' ' . $currency;
}
}
// composer.json (excerpt)
"autoload": {
"psr-4": {
"App\\": "app/"
},
"files": [
"app/Helpers.php"
]
}
Example of a helper function and how to autoload it.
Registering Custom Directories with Composer
For any new top-level directories you create under app/
(e.g., app/Services
, app/Repositories
), you'll need to ensure Composer can autoload them. This is typically done by adding them to the psr-4
section in your composer.json
file and then running composer dump-autoload
.
{
"autoload": {
"psr-4": {
"App\\": "app/",
"App\\Services\\": "app/Services/",
"App\\Repositories\\": "app/Repositories/",
"App\\Actions\\": "app/Actions/"
},
"files": [
"app/Helpers.php"
]
}
}
Updating composer.json
to autoload custom namespaces.
1. Create the Directory
Create your new directory, for example, app/Services
.
2. Add Namespace to composer.json
Open composer.json
and add an entry under autoload.psr-4
mapping your new namespace (e.g., "App\\Services\\": "app/Services/"
).
3. Dump Autoload Files
Run composer dump-autoload
from your terminal to regenerate Composer's autoload files. This makes your new classes available to your application.