Builder
laravel-service-builder
Creates services for orchestrating operations and actions
Overview
The laravel-service-builder agent creates service classes and actions that encapsulate business logic. It follows the single responsibility principle, creating focused classes that handle one specific operation or orchestrate multiple steps.
Responsibilities
- Service Classes - Multi-method services for related operations
- Action Classes - Single-purpose invokable classes
- Dependency Injection - Proper constructor injection
- Interface Binding - Contract-based service registration
- Error Handling - Custom exceptions and error states
- Unit Tests - Tests with mocked dependencies
What It Creates
| Component | Location | Purpose |
|---|---|---|
| Services | app/Services/ |
Multi-method service classes |
| Actions | app/Actions/ |
Single-purpose invokable classes |
| Contracts | app/Contracts/ |
Service interfaces |
| Tests | tests/Unit/ |
Service/action tests |
Generated Service Example
<?php
namespace App\Services;
use App\Models\Order;
use App\Models\User;
use App\Contracts\PaymentGatewayInterface;
use App\Contracts\InventoryServiceInterface;
use App\Events\OrderPlaced;
use App\Exceptions\InsufficientStockException;
use Illuminate\Support\Facades\DB;
class OrderService
{
public function __construct(
private PaymentGatewayInterface $paymentGateway,
private InventoryServiceInterface $inventory,
) {}
/**
* Create a new order and process payment.
*
* @throws InsufficientStockException
* @throws PaymentFailedException
*/
public function createOrder(User $user, array $items, string $paymentMethodId): Order
{
// Validate stock
foreach ($items as $item) {
if (!$this->inventory->hasStock($item['product_id'], $item['quantity'])) {
throw new InsufficientStockException($item['product_id']);
}
}
return DB::transaction(function () use ($user, $items, $paymentMethodId) {
// Create order
$order = Order::create([
'user_id' => $user->id,
'status' => 'pending',
'total' => $this->calculateTotal($items),
]);
// Add line items
foreach ($items as $item) {
$order->items()->create($item);
}
// Process payment
$this->paymentGateway->charge(
amount: $order->total,
paymentMethodId: $paymentMethodId,
metadata: ['order_id' => $order->id],
);
// Reserve inventory
$this->inventory->reserve($items);
$order->update(['status' => 'paid']);
event(new OrderPlaced($order));
return $order;
});
}
private function calculateTotal(array $items): int
{
return collect($items)->sum(fn ($item) =>
$item['unit_price'] * $item['quantity']
);
}
}
Generated Action Example
<?php
namespace App\Actions;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
class CreateUserAction
{
/**
* Create a new user with profile.
*/
public function __invoke(array $data): User
{
return DB::transaction(function () use ($data) {
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
if (isset($data['profile'])) {
$user->profile()->create($data['profile']);
}
if (isset($data['roles'])) {
$user->roles()->attach($data['roles']);
}
return $user->load(['profile', 'roles']);
});
}
}
// Usage in controller
public function store(Request $request, CreateUserAction $createUser)
{
$user = $createUser($request->validated());
return redirect()->route('users.show', $user);
}
Service vs Action Pattern
| Use Service When | Use Action When |
|---|---|
| Related operations grouped together | Single operation with clear purpose |
| e.g., OrderService (create, cancel, refund) | e.g., CreateOrderAction |
| Shared state/dependencies | Stateless, composable |
| Complex domain with many operations | Simple, focused operations |
Testing Services
<?php
use App\Services\OrderService;
use App\Contracts\PaymentGatewayInterface;
use App\Contracts\InventoryServiceInterface;
it('creates an order with payment', function () {
$paymentGateway = Mockery::mock(PaymentGatewayInterface::class);
$paymentGateway->shouldReceive('charge')->once()->andReturn(true);
$inventory = Mockery::mock(InventoryServiceInterface::class);
$inventory->shouldReceive('hasStock')->andReturn(true);
$inventory->shouldReceive('reserve')->once();
$service = new OrderService($paymentGateway, $inventory);
$user = User::factory()->create();
$items = [['product_id' => 1, 'quantity' => 2, 'unit_price' => 1000]];
$order = $service->createOrder($user, $items, 'pm_test');
expect($order->status)->toBe('paid');
expect($order->total)->toBe(2000);
});
Invoked By Commands
- /laravel-agent:service:make - Create services/actions
Called By
- laravel-architect - When building business logic
Guardrails
The service builder follows strict rules:
- ALWAYS use constructor injection for dependencies
- ALWAYS define return types and exceptions in docblocks
- ALWAYS wrap multi-step operations in transactions
- NEVER inject Request objects into services
- NEVER create services with more than 7 methods
See Also
- laravel-module-builder - For complete domain modules
- laravel-feature-builder - For full-stack features