info@a-coding-project.de

Laravel Service Container

Der Service Container ist ein zentraler Teil jeder Laravel-Anwendung und hilft dabei, das Konzept der Dependency Injection umzusetzen.

Was ist Dependency Injection?

Wo immer im Code Abhängigkeiten zu einer anderen Klasse der Anwendung bestehen, lässt sich diese einfach direkt an beliebiger Stelle mittels „new“ instanziieren. Der Nachteil dieser Vorgehensweise ist, dass man sich dann auch mit der Art und Weise der Instanziierung dieser Klassse auseinandersetzen muss und gegebenenfalls auch deren eigenen Klassen-Abhängigkeiten zunächst instanziieren muss. Das mag bei einem überschaubaren Projekt kein großes Problem sein, bei größeren Anwendungen aber können die internen Abhängigkeiten von Klassen untereinander recht komplex und unübersichtlich werden.

Dependency Injection lässt den Entwickler Instanzen von Klassen nutzen ohne die Instanziierung an selber Stelle konkret implementieren zu müssen. Die Instanziierung muss nur ein mal definiert werden. Dann kann ihre Ausführung dem sogenannten Service Container überlassen werden.

Dependency Injection einsetzen

Eine simple Art, Dependency Injection zu nutzen ist, eine Klasse als Parameter in den Konstruktor einer anderen Klasse zu injizieren. Als Beispiel definieren wie eine Klasse, die uns dabei helfen soll, Entfernungen zwischen Geo-Koordinaten zu messen. Wir nennen diese Klasse GeoService und legen sie in unser app-Verzeichnis.

namespace App;

class GeoService
{

    public function getDistance(float $latitudeFrom, float $longitudeFrom, float $latitudeTo, float $longitudeTo): float
    {
        // komplizierte Berechnungen...
        // ...

        return $distance;
    }
}

Die nutzende Klasse bekommt diese Service-Klasse nun in den Konstruktor injiziert.

namespace App\Http\Controllers;

use App\GeoService;

class DIExampleController extends Controller
{
    private $geoService = null;

    public function __construct(GeoService $g)
    {
        $this->geoService = $g;
    }

    public function index() {
        $distance = $this->geoService->getDistance(51.964153, 7.630075, 52.373225, 4.902462);

        return view('examples.geo', ['distance' => $distance]);
    }
}

Der GeoService ist nun schon ohne weiteres nutzbar. Die Instanziierung war aber auch sehr simpel und wir haben für den Service keinen eigenen Konstruktor definieren müssen. Oft ist aber die Instanziierung nicht ganz so einfach und es müssen zum Beispiel noch bestimmte Parameter an das neue Objekt übergeben werden. In solchen Fällen kann man mit Bindings arbeiten.

Bindings erzeugen

Per Binding lässt sich dem Service Container mitteilen, wie genau eine bestimmte Klasse zu erzeugen ist. Bindings werden in den Provider-Klassen definiert, die sich standardmäßig in app\Providers befinden. Hier können entweder eigene Provider-Klassen hinzugefügt oder ein neues Binding in einem bestehenden Provider definiert werden.

Im Folgenden nehmen wir an der GeoService aus dem vorherigen Beispiel bräuchte nun noch einen Wert bei der Instanziierung - zum Beispiel den Erdradius.

class GeoService
{
    private $earthradius;

    public function __construct($earthradius)
    { 
        $this->earthradius = $earthradius;
    }

Dann könnte man ein neues Binding in app\Providers\AppServiceProvider.php in der register-Methode hinzufügen.

/**
* Register any application services.
*
* @return  void
*/
public function register()
{
    //
    $this->app->bind(GeoService::class, function($app) {
        $earthRadius = 6371000;
        return new GeoService($earthRadius);
    });
}

(Besser wäre es natürlich, den Wert in eine Config-Datei auszulagern)

Somit hätte man die Erzeugung der GeoService-Klasse erfolgreich registriert. Überall, wo nun per Dependency Injection ein GeoService genutzt wird, enthält dieser bereits den entsprechenden Wert für den Erdradius.

Allerdings ist zu beachten, dass die per bind-Methode erzeugten Objekte - unabhängig davon ob sie bereits existieren - immer wieder neu instanziiert werden. Um sicherzustellen, dass ein Objekt einer Klasse nur einziges Mal instanziiert wird, sollte man stattdessen die singleton-Methode benutzen.

$this->app->singleton(GeoService::class, function($app) {
    return new GeoService(6371000);
});

Das $app-Objekt gibt uns Zugriff auf den Service Container. Das erlaubt es uns, hier noch weitere Objekte zu instanziieren und bei Bedarf auch in das neu erzeugte Objekt zu injizieren.

$this->app->singleton(GeoService::class, function($app) {
    return new GeoService(6371000, $app->make(MapService::class));
});

Komponenten noch weiter entkoppeln mittels Interfaces

Die Objekterzeugung wurde in den vorangegangenen Beispielen erfolgreich aus dem Programmcode des Controllers in den Provider ausgelagert. Allerdings blieb dabei noch eine direkte Referenz zum GeoService im Konstruktor bestehen:
public function __construct(GeoService $g).

Wenn irgendwann einmal der GeoService durch einen anderen Service (zum Beispiel von einem Drittanbieter) ersetzt werden soll, müsste der Name des anderen Service an dieser und vermutlich vielen anderen Stellen geändert werden. Unter Umständen hat der neue Service auch ganz andere Methoden-Namen, Rückgabewerte etc. In solchen Fällen ist es sinnvoll mit Interfaces zu arbeiten.

Bindings können dabei helfen ein Mapping zwischen Interface und konkreter Implementierung zu schaffen.

$this->app->bind(
    'App\GeoServiceInterface',
    'App\GeoService'
);

Möchte ein Controller den Service nutzen, injizieren wir dann nicht mehr die konkrete Klasse sondern das Interface:

public function __construct(GeoServiceInterface $g)

Fazit

Der Service Container hilft uns im wesentlichen dabei, unsere Klassenabhängigkeiten mittels Dependency Injection und die Erzeugung von Objekten zu organisieren. Provider-Klassen stellen dabei den zentralen Ort dar, an dem die Konfiguration dafür erfolgt.

Dependency Injection hilft uns dabei, einzelne Komponenten unserer Anwendung bestmöglich voneinander zu entkoppeln. Entkoppelte Komponenten sind einfacher zu verändern, auszutauschen und zu testen und erhöhen damit die Wartbarkeit und Weiterentwickelbarkeit einer Anwendung.
Geringe Kopplung ist eines der wichtigsten Prinzipien der Softwareentwicklung und bildet das „D“ in den „S.O.L.I.D“ – Prinzipien des Softwaredesigns.

Laravel Entwickler gesucht?

Wir haben bereits viel Erfahrung im Umgang mit Laravel und Vue.js.
Nehmen Sie gerne unverbindlich Kontakt zu uns auf.

zum Angebot

Über uns

Stefan Wienströer

Wir entwickeln Webanwendungen mit viel Leidenschaft. Unser Wissen geben wir dabei gerne weiter. Mehr über a coding project

Auch interessant