Service classes are a popular way to extract application logic and keep your code DRY. The best way to learn is to look at practical examples. In this post, we'll examine ten examples from Laravel open-source projects and how they use Services differently.
The article is very long, so here's the Table of Contents:
- UserCreationService: Used in Controllers and Artisan Command
- IndexCreditCards: Simple Method with Multiple Usage
- InvoiceCalculator: Many Methods around Same Topic
- CreateProjectLink: Service with Private Methods
- InvoiceService Used in Another Service
- VisitorService: One "Main" and Two "Extra" Methods
- AppointmentService Used in Two Controllers
- AuthService to Work with Auth Tokens
-
CancelAccount with
execute()
: Similar to Queueable Job - ExchangeRateHost with Interface Contract
All the examples include links to GitHub repositories so you can explore them further. Let's go!
Example 1: UserCreationService: Used in Controllers and Artisan Command
The first example is from an open-source project called pterodactyl/panel. Here, we have a UserCreationService
service, which creates a user using the handle()
method.
app/Services/Users/UserCreationService.php:
class UserCreationService{ public function handle(array $data): User { if (array_key_exists('password', $data) && !empty($data['password'])) { $data['password'] = $this->hasher->make($data['password']); } $this->connection->beginTransaction(); if (!isset($data['password']) || empty($data['password'])) { $generateResetToken = true; $data['password'] = $this->hasher->make(str_random(30)); } /** @var \Pterodactyl\Models\User $user */ $user = $this->repository->create(array_merge($data, [ 'uuid' => Uuid::uuid4()->toString(), ]), true, true); if (isset($generateResetToken)) { $token = $this->passwordBroker->createToken($user); } $this->connection->commit(); $user->notify(new AccountCreated($user, $token ?? null)); return $user; }}
This service method is called in a few places: two Controllers and one Artisan command.
Reusability is one of the benefits of Services.
app/Http/Controllers/Admin/UserController.php:
// ... use Pterodactyl\Services\Users\UserCreationService; class UserController extends Controller{ public function __construct( protected AlertsMessageBag $alert, protected UserCreationService $creationService, // ... ) { } public function store(NewUserFormRequest $request): RedirectResponse { $user = $this->creationService->handle($request->normalize()); $this->alert->success($this->translator->get('admin/user.notices.account_created'))->flash(); return redirect()->route('admin.users.view', $user->id); } // ...}
app/Console/Commands/User/MakeUserCommand.php:
use Pterodactyl\Services\Users\UserCreationService; class MakeUserCommand extends Command{ // ... public function __construct(private UserCreationService $creationService) { parent::__construct(); } public function handle() { // ... $user = $this->creationService->handle(compact('email', 'username', 'name_first', 'name_last', 'password', 'root_admin')); // ... }}
Example 2: IndexCreditCards: Simple Method with Multiple Usage
This example comes from the serversideup/financial-freedom open-source project.
The primary Service method is simple: just getting data from the database.
app/Services/CreditCards/IndexCreditCards.php:
use App\Models\CreditCard; class IndexCreditCards{ public function index() { $creditCards = CreditCard::with('institution') ->with('rules') ->where('user_id', auth()->id()) ->orderBy('name', 'ASC') ->get(); return $creditCards; }}
The benefit is that it is used in multiple Controllers.
app/Http/Controllers/TransactionController.php:
use App\Services\CreditCards\IndexCreditCards; // ... class TransactionController extends Controller{ public function index( Request $request ): Response { return Inertia::render( 'Transactions/Index', [ 'group' => 'transactions', 'transactions' => fn () => ( new IndexTransactions() )->execute( $request ), 'groups' => fn () => ( new IndexGroups() )->index( $request ), 'cashAccounts' => fn() => ( new IndexCashAccounts() )->index(), 'creditCards' => fn() => ( new IndexCreditCards() )->index(), 'loans' => fn() => ( new IndexLoans() )->index(), 'filters' => $request->all() ] ); } // ...}
app/Http/Controllers/AccountController.php:
use App\Services\CreditCards\IndexCreditCards; // ... class AccountController extends Controller{ public function index( Request $request ): Response { return Inertia::render('Accounts/Index', [ 'group' => 'accounts', 'cashAccounts' => fn() => ( new IndexCashAccounts() )->index(), 'creditCards' => fn() => ( new IndexCreditCards() )->index(), 'loans' => fn() => ( new IndexLoans() )->index(), 'institutions' => fn () => ( Institution::orderBy('name', 'ASC')->get() ), ]); } // ...}
Example 3: InvoiceCalculator: Many Methods around Same Topic
In this example, we have the Bottelet/DaybydayCRM open-source project.
The first two examples were more like Action classes of doing one thing. But a more typical case is a Service class with multiple methods working with the same object or topic, like invoices, in this case.
The InvoiceCalculator
service has six methods for invoice calculations.
It's one of the differences between the Service and Action classes: multiple methods instead of just a single handle()
or similar. It's one of the most popular lessons in our course How to Structure Laravel Projects.
app/Services/Invoice/InvoiceCalculator.php:
use App\Models\Offer;use App\Models\Invoice;use App\Repositories\Tax\Tax;use App\Repositories\Money\Money; class InvoiceCalculator{ private $invoice; private $tax; public function __construct($invoice) { if(!$invoice instanceof Invoice && !$invoice instanceof Offer ) { throw new \Exception("Not correct type for Invoice Calculator"); } $this->tax = new Tax(); $this->invoice = $invoice; } public function getVatTotal() { $price = $this->getSubTotal()->getAmount(); return new Money($price * $this->tax->vatRate()); } public function getTotalPrice(): Money { $price = 0; $invoiceLines = $this->invoice->invoiceLines; foreach ($invoiceLines as $invoiceLine) { $price += $invoiceLine->quantity * $invoiceLine->price; } return new Money($price); } public function getSubTotal(): Money { $price = 0; $invoiceLines = $this->invoice->invoiceLines; foreach ($invoiceLines as $invoiceLine) { $price += $invoiceLine->quantity * $invoiceLine->price; } return new Money($price / $this->tax->multipleVatRate()); } public function getAmountDue() { return new Money($this->getTotalPrice()->getAmount() - $this->invoice->payments()->sum('amount')); } public function getInvoice() { return $this->invoice; } public function getTax() { return $this->tax; }}
For example, InvoicesController
uses the Service to get various invoice information.
app/Http/Controllers/InvoicesController.php:
use App\Models\Invoice;use App\Services\Invoice\InvoiceCalculator; class InvoicesController extends Controller{ // ... public function show(Invoice $invoice) { // ... $invoiceCalculator = new InvoiceCalculator($invoice); $totalPrice = $invoiceCalculator->getTotalPrice(); $subPrice = $invoiceCalculator->getSubTotal(); $vatPrice = $invoiceCalculator->getVatTotal(); $amountDue = $invoiceCalculator->getAmountDue(); // ... } // ...}
Example 4: CreateProjectLink: Service with Private Methods
In this example, we...