Service Layer
The service layer encapsulates business logic and orchestrates between controllers, repositories, actions, and pipelines. Controllers never access repositories directly — they always go through a service.
Controller → Service → Repository / Action / Pipeline → ModelDirectory Structure
app/Services/
├── AmslerGrid/
│ └── AmslerGridService.php
├── Auth/
│ ├── LoginService.php
│ └── ...
├── Branch/
│ └── BranchService.php
├── Medicine/
│ └── MedicineService.php
├── Patient/
│ └── PatientService.php
├── Queue/
│ └── QueueService.php
├── Transaction/
│ └── TransactionService.php
├── Upload/
│ └── UploadService.php
└── ... (41 services total)Service Patterns
There are two main service patterns in the codebase:
Pattern 1: Action-Based Service
Used for simple CRUD modules. The service delegates to injected Action classes:
class MedicineService
{
public function __construct(
private CreateMedicineAction $createAction,
private UpdateMedicineAction $updateAction,
private DeleteMedicineAction $deleteAction,
private ToggleMedicineStatusAction $toggleAction,
private FilterableRepositoryInterface $repository,
) {}
public function getPaginated(?string $search, ?array $filters, int $perPage)
{
return $this->repository->getPaginated($search, $filters, $perPage);
}
public function create(array $data): Medicine
{
return $this->createAction->execute($data);
}
public function update(Medicine $medicine, array $data): Medicine
{
return $this->updateAction->execute($medicine, $data);
}
public function delete(Medicine $medicine): bool
{
return $this->deleteAction->execute($medicine);
}
public function toggleStatus(Medicine $medicine, string $status): Medicine
{
return $this->toggleAction->execute($medicine, $status);
}
}This pattern is used by: MedicineService, InsuranceService, ProcedureService, PharmacyItemService, ServiceService, StockAvailableService, BranchService.
Pattern 2: Pipeline-Based Service
Used for complex creation flows. The service sends a DTO through a Pipeline:
class PatientService
{
public function __construct(
private PatientRepository $patientRepository,
private UploadService $uploadService,
) {}
public function create(array $params, Branch $branch): Patient
{
$data = new AddNewPatientData($params, $branch);
Pipeline::send($data)
->through([
UploadPhotoPipeline::class,
CreateNewPatientPipeline::class,
GeneratePatientCodePipeline::class,
])
->thenReturn();
return $data->patient;
}
}This pattern is used by: PatientService, auth-related services (login, password reset).
Pattern 3: Direct Repository Service
Used for transactional operations that need fine-grained control:
class TransactionService
{
public function __construct(
private TransactionRepository $repository,
private StockMovementService $stockMovementService,
) {}
public function create(array $data): Transaction
{
return DB::transaction(function () use ($data) {
$data['transaction_number'] = 'TRX-' . strtoupper(Str::random(10));
$data['branch_id'] = auth()->user()->default_branch_id;
$totalAmount = collect($data['items'])->sum(fn ($item) =>
$item['quantity'] * $item['unit_price']
);
$data['total_amount'] = $totalAmount;
$transaction = $this->repository->create($data);
foreach ($data['items'] as $item) {
$transaction->items()->create([
'pharmacy_item_id' => $item['pharmacy_item_id'] ?? null,
'name' => $item['name'],
'quantity' => $item['quantity'],
'unit_price' => $item['unit_price'],
'subtotal' => $item['quantity'] * $item['unit_price'],
]);
}
if ($data['status'] === 'Completed') {
$this->deductStock($transaction);
}
return $transaction;
});
}
}This pattern is used by: TransactionService, DeliveryService, QueueService.
Actions
Actions are single-responsibility command classes that encapsulate a discrete operation:
app/Actions/
├── Branch/
│ ├── CreateBranchAction.php
│ ├── UpdateBranchAction.php
│ ├── DeleteBranchAction.php
│ └── ToggleBranchStatusAction.php
├── Medicine/
│ ├── CreateMedicineAction.php
│ ├── UpdateMedicineAction.php
│ ├── DeleteMedicineAction.php
│ └── ToggleMedicineStatusAction.php
├── Fortify/
│ ├── CreateNewUser.php
│ └── ResetUserPassword.php
└── ... (actions for Insurance, Procedure, Service, etc.)Example action:
class CreateBranchAction
{
public function __construct(
private RepositoryInterface $repository
) {}
public function execute(array $data): Branch
{
return $this->repository->create($data);
}
}The concrete repository is injected via contextual binding in RepositoryServiceProvider:
$this->app->when([CreateBranchAction::class, UpdateBranchAction::class, ...])
->needs(RepositoryInterface::class)
->give(BranchRepository::class);DTOs (Data Transfer Objects)
DTOs carry data between layers, especially through pipelines:
app/Dtos/
├── Patient/
│ └── AddNewPatientData.php
├── Auth/
│ ├── ResetCodeData.php
│ ├── VerifyCodeData.php
│ ├── ResetPasswordData.php
│ └── SwitchBranchData.php
├── BillItem/
│ ├── CreateBillItemData.php
│ └── UpdateBillItemData.php
├── Upload/
│ └── UploadImageData.php
└── AuthTokenResult.phpExample DTO:
class AddNewPatientData
{
public Patient $patient; // Populated by the pipeline
public function __construct(
public array $params,
public Branch $branch,
) {}
}Dependency Injection
Services and Actions receive their dependencies via constructor injection. The RepositoryServiceProvider handles all the bindings:
// Interface bindings
$this->app->bind(BranchRepositoryInterface::class, BranchRepository::class);
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
// Contextual bindings: Actions get specific repositories
$this->app->when([CreateMedicineAction::class, UpdateMedicineAction::class, ...])
->needs(RepositoryInterface::class)
->give(MedicineRepository::class);
// Services get specific filterable repositories
$this->app->when(MedicineService::class)
->needs(FilterableRepositoryInterface::class)
->give(MedicineRepository::class);Creating a New Service
- Create the service class in
app/Services/{Module}/ - Inject the repository (or actions) via constructor
- Register bindings in
RepositoryServiceProviderif using interfaces - Inject the service into the controller via constructor