Service Classes in Laravel: 10 Open-Source Practical Examples

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:

  1. UserCreationService: Used in Controllers and Artisan Command
  2. IndexCreditCards: Simple Method with Multiple Usage
  3. InvoiceCalculator: Many Methods around Same Topic
  4. CreateProjectLink: Service with Private Methods
  5. InvoiceService Used in Another Service
  6. VisitorService: One "Main" and Two "Extra" Methods
  7. AppointmentService Used in Two Controllers
  8. AuthService to Work with Auth Tokens
  9. CancelAccount with execute(): Similar to Queueable Job
  10. 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...

The full tutorial [21 mins, 4003 words] is only for Premium Members

Login Or Become a Premium Member for $129/year or $29/month
What else you will get:
  • 68 courses (1183 lessons, total 43 h 18 min)
  • 90 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent New Courses