Laravel Pipelines is a lesser-known but powerful feature of Laravel that is not documented in the official Laravel documentation. However, this tool is widely used within the framework for performing sequential operations. In this article, we will explore how Laravel Pipelines can be beneficial in CRM development and provide examples of its use, adhering to clean architecture principles and Domain-Driven Design (DDD).
Where Laravel Pipelines Can Be Beneficial
Consider a scenario where you have some initial data that needs to undergo a series of sequential actions. For example, adding a new contact to a CRM system.
Example 1: Adding a New Contact
When a user adds a new contact, the data is sent to the controller. The contact goes through several steps before being saved to the database:
- Check for the presence of all required fields.
- Data validation: Ensure the correctness of the email, phone number, etc.
- Remove extra whitespace and format text.
- Check for unique contact.
- Generate a unique identifier for the contact.
- Save the contact to the database.
Example 2: Handling a Support Request
When a user creates a support request, the request data also goes through several steps:
- Check for the presence of all required fields.
- Data validation: Ensure the correctness of the email, problem description, etc.
- Remove extra whitespace and format text.
- Generate a unique identifier for the request.
- Save the request to the database.
- Send a notification to the user.
- Assign the request to a support staff member.
Code Evolution
Let's see how the code evolves as new steps are added, adhering to clean architecture principles and DDD.
Initial Stage
Initially, we have a controller and a service that handles the core logic. For example, adding a simple contact:
public function store(Request $request, ContactService $contactService)
{
$contact = $contactService->create($request->all());
return response()->json($contact, 201);
}
class ContactService
{
protected $contactRepository;
public function __construct(ContactRepository $contactRepository)
{
$this->contactRepository = $contactRepository;
}
public function create(array $data)
{
return $this->contactRepository->create($data);
}
}
class ContactRepository
{
public function create(array $data)
{
return Contact::create($data);
}
}
Adding New Steps
As new steps are added, the code becomes more complex. We move the logic to separate classes:
public function store(Request $request, ContactService $contactService)
{
$contact = $contactService->create($request->all());
return response()->json($contact, 201);
}
class ContactService
{
protected $contactRepository;
public function __construct(ContactRepository $contactRepository)
{
$this->contactRepository = $contactRepository;
}
public function create(array $data)
{
$data = (new TrimWhitespace())->handle($data);
$data = (new ValidateUniqueEmail())->handle($data);
$data = (new GenerateUniqueId())->handle($data);
return $this->contactRepository->create($data);
}
}
Unifying Interfaces
We can unify the interfaces and use Laravel Pipelines for sequential execution of steps:
public function store(Request $request, ContactService $contactService)
{
$contact = $contactService->create($request->all());
return response()->json($contact, 201);
}
class ContactService
{
protected $contactRepository;
public function __construct(ContactRepository $contactRepository)
{
$this->contactRepository = $contactRepository;
}
public function create(array $data)
{
$pipes = [
TrimWhitespace::class,
ValidateUniqueEmail::class,
GenerateUniqueId::class,
];
$data = app(Pipeline::class)
->send($data)
->through($pipes)
->then(function ($data) {
return $this->contactRepository->create($data);
});
return $data;
}
}
Example Usage of Laravel Pipelines
Pipe Interface
Each step (pipe) should implement the Pipe interface:
namespace App\Pipes;
use Closure;
interface Pipe
{
public function handle($data, Closure $next);
}
Pipe Example
namespace App\Pipes;
use Closure;
class TrimWhitespace implements Pipe
{
public function handle($data, Closure $next)
{
$data['name'] = trim($data['name']);
return $next($data);
}
}
Associated Issues
Sometimes, you need to interrupt the execution at a certain step or clean up the database from previously executed steps. Here is an example of how to handle such issues:
class ContactService
{
protected $contactRepository;
public function __construct(ContactRepository $contactRepository)
{
$this->contactRepository = $contactRepository;
}
public function create(array $data)
{
$pipes = [
TrimWhitespace::class,
ValidateUniqueEmail::class,
GenerateUniqueId::class,
];
try {
DB::beginTransaction();
$data = app(Pipeline::class)
->send($data)
->through($pipes)
->then(function ($data) {
return $this->contactRepository->create($data);
});
DB::commit();
return $data;
} catch (Exception $e) {
DB::rollback();
throw $e;
}
}
}
Here, a transaction is used to ensure data integrity. In case of an error, the transaction is rolled back (`DB::rollback()`), and an error message is returned.
Conclusion
Laravel Pipelines is a powerful tool for managing sequential operations. It helps avoid spaghetti code and makes the code cleaner and more maintainable. In this article, we explored examples of using Laravel Pipelines for managing contacts and support requests in a CRM system, adhering to clean architecture principles and DDD. We hope this information will be useful for your project development.