0% прочитано

SOLID. Principle 5 - Dependency Inversion Principle in Practice in a Real Laravel Project

Using real Analytics, IndexNow, and Media cases from ICanUp, I explain Dependency Inversion Principle: application code depends on contracts while MySQL, HTTP, and FFprobe remain infrastructure details.

23 вересня 2026 р. 9 хв читанняLaravel

In a growing Laravel project, application code quickly starts working with databases, HTTP, cache, filesystems, and external processes.

The problem begins when business or application logic directly knows about a specific MySQL repository, Laravel HTTP client, or the ffprobe command.

In ICanUp, those details are hidden behind contracts in several important places. This is where Dependency Inversion Principle becomes practical.

Dependency Inversion Principle

Dependency Inversion Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

In simpler terms: important application logic should know what it needs, but it should not decide which concrete infrastructure technology must provide it.

Example 1 - Analytics Does Not Depend Directly on MySQL

PHP - Analytics query depends on a contract
interface AnalyticsAggregationRepositoryInterface{    public function aggregate(        AnalyticsAggregationQuery $query,    ): iterable;} final readonly class ContentAnalyticsQuery{    public function __construct(        private AnalyticsAggregationRepositoryInterface $aggregationRepository,        private AnalyticsPeriodResolver $periodResolver,    ) {}}

ContentAnalyticsQuery needs aggregated analytics data. It does not need to know which table stores that data, which SQL is executed, or whether caching is involved.

It therefore depends on AnalyticsAggregationRepositoryInterface. From the application layer point of view, the required capability is simply aggregate().

PHP - AnalyticServiceProvider binds the detail
$this->app->bind(    AnalyticsAggregationRepositoryInterface::class,    static fn (        Application $app,    ): CachedAnalyticsAggregationRepository =>        new CachedAnalyticsAggregationRepository(            source: $app->make(                MySqlAnalyticsAggregationRepository::class,            ),            cache: $app->make(                AnalyticsAggregationCache::class,            ),        ),);

The concrete composition happens in AnalyticServiceProvider: the contract resolves to a cached repository that internally uses MySqlAnalyticsAggregationRepository.

If persistence or caching changes, ContentAnalyticsQuery does not need to become a place full of MySQL or cache details.

The dependency points from application code toward the abstraction, while infrastructure adapts to that contract.

Example 2 - The IndexNow Job Does Not Know How HTTP Is Sent

PHP - IndexNow client contract and job
interface IndexNowClientInterface{    public function submit(array $urls): void;} final class SubmitIndexNowUrlsJob implements ShouldQueue{    public function handle(        IndexNowClientInterface $client,    ): void {        if (! (bool) config('indexnow.enabled')) {            return;        }         $client->submit($this->urls);    }

SubmitIndexNowUrlsJob has a simple task: pass URLs for submission to IndexNow.

The job does not build the HTTP payload, read the endpoint, host, key, or timeout, and it does not call Http::post(). It depends on IndexNowClientInterface and only calls submit().

PHP - SeoServiceProvider binding
$this->app->bind(    IndexNowClientInterface::class,    IndexNowHttpClient::class,);

The infrastructure implementation IndexNowHttpClient knows the HTTP details: configuration, host, key, endpoint, timeout, and the Laravel Http facade.

Laravel Service Container connects the contract to that implementation while the job remains independent of the concrete HTTP mechanism.

Example 3 - Media Validation Does Not Depend Directly on FFprobe

PHP - Video metadata contract and validator
interface VideoMetadataReaderInterface{    public function read(        UploadedFile $file,    ): VideoMetadata;} final readonly class MediaUploadValidator{    public function __construct(        private MediaTypeResolver $mediaTypeResolver,        private MediaValidationConfigFactory $configFactory,        private VideoMetadataReaderInterface $videoMetadataReader,    ) {}     private function validateVideo(

MediaUploadValidator needs video width, height, and duration so it can apply validation rules.

It does not need to know that those values are currently produced by running the external ffprobe process. The validator therefore depends on VideoMetadataReaderInterface.

The concrete FFprobeVideoMetadataReader runs Symfony Process and converts its output into VideoMetadata. That is an infrastructure detail.

PHP - MediaServiceProvider binding
$this->app->bind(    VideoMetadataReaderInterface::class,    FFprobeVideoMetadataReader::class,);

An Interface Alone Does Not Mean Dependency Inversion

For DIP, simply putting SomethingInterface in front of a concrete class is not enough.

I look at the dependency direction. If application code still creates new MySqlRepository(), calls Http::post(), or runs ffprobe directly, the abstraction has not really separated high-level logic from details.

In these examples, the contracts sit exactly at the boundary between application or domain needs and infrastructure implementations.

What Laravel Service Container Does Here

Service Container is not Dependency Inversion Principle by itself. It is a tool that helps compose dependencies in one place.

An application class asks for a contract. A Service Provider decides which concrete implementation will satisfy it. Infrastructure choices therefore do not spread across application code.

Practical Takeaway

My simplest formulation of Dependency Inversion Principle is: important logic should depend on the capability it needs, not on the concrete technology that provides that capability.

Analytics needs aggregated data, not MySQL. The IndexNow job needs URL submission, not the Laravel HTTP facade. Media validation needs video metadata, not FFprobe.

This keeps infrastructure replaceable or composable without pulling its details into code that solves the main application problem.