Skip to content

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 → Model

Directory 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:

php
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:

php
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:

php
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:

php
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:

php
$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.php

Example DTO:

php
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:

php
// 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

  1. Create the service class in app/Services/{Module}/
  2. Inject the repository (or actions) via constructor
  3. Register bindings in RepositoryServiceProvider if using interfaces
  4. Inject the service into the controller via constructor

CPR - Clinical Patient Records