Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk5 min

Laravel Hexagonal Architecture: Where to Put Ports, Adapters, and Container Bindings

A practical Laravel guide to ports and adapters: define meaningful application boundaries, wire implementations with the service container, and test behavior independently.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To decouple a Laravel app, keep application rules behind boundaries you own and place Laravel, database, queue, mail, and vendor-specific code in adapters around them. Hexagonal architecture is a way to organize those boundaries—not a Laravel-mandated folder layout. Laravel’s service container can connect an application-owned interface to an adapter and inject that adapter where needed.

What hexagonal architecture means in Laravel

Hexagonal architecture—also called ports and adapters—puts the application’s meaningful behavior at the center. That core describes what the application needs to do; it should not have to know which framework API, database driver, payment provider, or queue transports the request.

As an Amazon Associate I earn from qualifying purchases.

A port is an interface describing a capability the core needs, such as charging a payment or storing an order. An adapter implements that interface using a specific technology. Laravel’s container wires the two together. The dependency direction matters: the application defines the need, and an outer adapter satisfies it. A provider change should therefore affect the adapter and its wiring, not rewrite the business rule.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This is an architectural choice, not a framework requirement. Laravel applications can use many structures; create boundaries where they protect behavior or make a dependency replaceable, not merely to reproduce a diagram.

How do I use hexagonal architecture in Laravel?

Start with a real application behavior and a dependency that crosses an important boundary. For example, a payment use case can depend on an application-owned PaymentGateway port. A provider-specific adapter implements it, while a Laravel service provider binds the port to that adapter.

1. Define the application-owned port

<?php

namespace AppApplicationPayments;

interface PaymentGateway
{
    public function charge(int $amountInCents, string $paymentToken): string;
}

The interface expresses what the application needs: a charge request and a resulting identifier. It does not expose a vendor SDK type or require the use case to know how payments are sent.

2. Make the use case depend on the port

<?php

namespace AppApplicationPayments;

final class PlaceOrder
{
    public function __construct(private PaymentGateway $payments)
    {
    }

    public function handle(int $amountInCents, string $paymentToken): string
    {
        return $this->payments->charge($amountInCents, $paymentToken);
    }
}

The example keeps the boundary visible through constructor injection. A real use case would also apply the relevant application rules; the point is that those rules call the port rather than a provider SDK directly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Implement an infrastructure adapter

<?php

namespace AppInfrastructurePayments;

use AppApplicationPaymentsPaymentGateway;

final class ProviderPaymentGateway implements PaymentGateway
{
    public function charge(int $amountInCents, string $paymentToken): string
    {
        // Translate the application request to the provider SDK/API.
        // Translate the provider response back to an application value.
    }
}

The adapter is the place for provider-specific requests, response mapping, and error translation. The abbreviated method body is illustrative; implement the provider call and its failure behavior for the integration you actually use.

4. Bind the port in a service provider

<?php

namespace AppProviders;

use AppApplicationPaymentsPaymentGateway;
use AppInfrastructurePaymentsProviderPaymentGateway;
use IlluminateSupportServiceProvider;

final class PaymentServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(PaymentGateway::class, ProviderPaymentGateway::class);
    }
}

Ensure the provider is registered for the Laravel application, using the registration mechanism appropriate to its version and setup. Laravel’s service container supports binding an interface to an implementation and resolving dependencies for classes Laravel manages, including controllers, listeners, middleware, and queued jobs. See Laravel 13.x service container documentation.

When Laravel resolves a controller that receives PlaceOrder in its constructor, it can resolve that class and its dependencies; the binding tells the container which concrete gateway to supply for PaymentGateway. Classes created manually with new are not automatically resolved by the container, so their dependencies must be supplied by the code that constructs them.

Where Laravel contracts and facades fit

Laravel contracts are interfaces for core framework services. They are distinct from application-owned ports: a framework contract names a Laravel service, while an application port can define a capability in the language and terms of the application. Choosing an application-owned port is a design recommendation when it clarifies or protects a boundary, not a Laravel rule. Laravel explains its contracts and facades at Contracts | Laravel 12.x; that page identifies itself as an older version and recommends Laravel 13.x.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Contracts and facades are both legitimate ways to use Laravel services. Constructor injection makes a dependency explicit at the class boundary; facades can be convenient where the team prefers that style. Laravel says the choice comes down to personal or team preference and that either can support robust, well-tested applications.

Choice Dependency visibility Convenience Useful context
Constructor-injected contract or application port Visible in the constructor signature Requires dependencies to be passed or container-resolved Helpful when a class boundary should show its needs clearly
Facade Used at the call site rather than listed as a constructor dependency Direct and concise access to a Laravel service Valid for application code when it fits team conventions

Laravel’s current package-development guidance says contracts and facades offer essentially equal testability when writing an application. Package authors have a different context: Laravel’s official package skeleton includes testing, static-analysis, and formatting support. See Laravel 13.x package development documentation. These distinctions do not make every class a candidate for an interface; an abstraction is useful when it names a meaningful boundary or provides a practical substitution point.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How to test the application boundary

Test the use case with a fake implementation of its port so the test can focus on application behavior without making a real external call. Laravel’s container documentation notes that injected services can be mocked or replaced with dummy implementations.

<?php

use AppApplicationPaymentsPaymentGateway;
use AppApplicationPaymentsPlaceOrder;

$fake = new class implements PaymentGateway {
    public function charge(int $amountInCents, string $paymentToken): string
    {
        return 'payment-test-123';
    }
};

$useCase = new PlaceOrder($fake);
$result = $useCase->handle(2500, 'test-token');

assert($result === 'payment-test-123');

A focused test can also assert that the use case passes the expected amount and token, and that it handles the returned result as intended. Then test the real adapter separately at the integration boundary, verifying its request and response mapping with an appropriate test setup for the provider. A fake proves the application can work against the port; it does not prove the provider adapter communicates correctly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to decouple an existing Laravel app gradually

Do not begin by moving every class or adding an interface to every dependency. Find a dependency that changes often, is costly or risky to call in ordinary tests, or sits beside independently meaningful application behavior. Extract only the boundary that makes that concrete problem easier to manage.

  1. Choose one behavior. Identify a use case or rule that should remain stable if an external system changes.
  2. Separate the external detail. Move provider-specific or framework-specific translation to an adapter, keeping the application-facing need small and explicit.
  3. Wire it through the container. Bind the application-owned port to the current adapter in a service provider, then let Laravel resolve the relevant class.
  4. Test both sides appropriately. Use a fake at the application boundary and a separate integration test for the real adapter.
  5. Stop when the boundary solves the problem. Keep direct Laravel conveniences where they are clear and low-risk; add another abstraction only when it serves a real need.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.