Laravel 5: In wenigen Schritten zum Laravel 5 Package

/Rathes Sachchithananthan
Laravel 5: In wenigen Schritten zum Laravel 5 Package

Du willst ein eigenes Package für Laravel entwickeln? Als ich mich das erste Mal damit beschäftigt habe, fand ich es recht schwer brauchbare Informationen oder eine Anleitung dafür zu finden. Dieser Beitrag soll die Lücke füllen und dir eine Schritt für Schritt Anleitung liefern wie du für Laravel 5 eigene Packages entwickelst.

Allgemeines zu Laravel Packages

Laravel Packages sind eine Möglichkeit dein Laravel-Projekt um Funktionalitäten zu erweitern. An sich ist das Laravel Framework auch ein solches Paket und es kommt mit vielen weiteren Paketen, die du im vendor/ Verzeichnis deiner Laravel Installation findest. Diese müssen nicht unbedingt Laravel spezifische Pakete sein, die nur die Laravel Funktionalitäten erweitern, sondern können auch komplett von Laravel unabhängige Pakete sein. Ein Beispiel dafür ist das Carbon Paket, das nicht nur in Laravel funktioniert, sondern auch Standalone in anderen PHP-Projekten.

In diesem Beitrag zeige ich dir, wie du ganz einfach ein Package entwickelst, das die Laravel Funktionalität erweitert. Ich werde nur die ersten Schritte erläutern, aber ich denke, dass damit die grundlegendsten Fragen geklärt sein werden. Falls du trotzdem noch weitere Fragen haben solltest, kannst du dich gerne an mich wenden.

Voraussetzungen

Die folgenden Schritte sind darauf ausgelegt, dass das resultierende Package am Ende mit Laravel 5.5 funktioniert. Aber wir sind am Ende nicht auf diese Version limitiert. Je nach dem, wie du die Anforderungen im späteren Verlauf anpasst, kann das Package auch ohne Probleme mit den Laravel-Versionen 5.3 bis hin zu 5.6 funktionieren. Dafür musst du nicht einmal am Code etwas ändern.

Prinzipiell sollte das Package auch für die Versionen 5.0 - 5.1 einzurichten sein, aber da will ich dir nichts garantieren. Bedenke nämlich, dass du je mehr Versionen du unterstützt du auch beim Code vielmehr auch Rückwärtskompatibilität achten musst. So funktionieren Features aus 5.5 nicht in 5.1, das musst du beim Entwickeln eines Packages dann immer berücksichtigen.

Für das Entwickeln eines Laravel-Packages brauchst du mindestens composer als Tool. Da du dich mit Laravel beschäftigst, wirst du composer aber sehr wahrscheinlich bei dir bereits installiert haben. Falls nicht solltest du das definitiv nachholen. Eine Anleitung wie du Composer installierst, findest du im Getting Started Bereich der Composer Webseite.

Du solltest auch ein wenig Erfahrung mit Laravel gesammelt haben, da wir bei der Entwicklung eines Packages keine Laravel-Instanz haben werden, sondern in einem leeren Ordner anfangen werden.

Wenn du doch ein wenig unsicher bist, dann erstelle dir ein neues Projekt und entwickle das Package in diesem Projekt. Erstelle dir einfach einen neuen Ordner packages im Root-Verzeichnis des Projektes. Darin erstelle einen Ordner mit deinem Namen oder dem Namen deines Unternehmens (der vendor) und darin wiederum einen Ordner mit dem Namen des Packages.

Wenn ich zum Beispiel für mein Unternehmen Aheenam das Package Awesome erstellen will, dann habe arbeite ich im Ordner packages/aheenam/awesome/.

1. Die Struktur

Es gibt keine festen Vorgaben wie ein Package aufgebaut sein muss. Abgesehen von einigen Kleinigkeiten kannst du dein Packages so strukturieren wie du willst. Laravel stellt sich da in keinster Weise quer, sondern unterstützt dich dabei sogar.

Trotzdem macht es Sinn eine gewissen Struktur zu haben, um ein sauberes und wartbares Repository zu haben. Im Folgenden stelle ich dir die Struktur vor, die ich in meinen Projekten benutze.


├── database/
│   ├── .gitkeep
├── config/
│   ├── package-name.php
├── src/
│   ├── PackageNameServiceProvider.php
├── tests/
│   ├── TestCase.php
├── .gitignore
├── CHANGELOG.md
├── composer.json
├── LICENSE
├── phpunit.xml
├── README.md

Das ist quasi das Grundgerüst eines jeden Packages bei mir. Gehen wir diese mal einzeln durch. Den database/ Ordner kennst du wahrscheinlich bereits aus Laravel. Wie auch dort landen in diesem Ordner meine migrations, seeds, und factories.

In config/ befindet sich die Config-Datei, die später in den genauso benannten Ordner im Laravel-Projekt gepublished wird.

Im src/ Ordner befindet sich die komplette Logik des Laravel Packages. Das fängt mit dem ServiceProvider an und kennt ab dann keine Grenzen.

Wie du dir wahrscheinlich denken kannst, befinden sich im tests/ Ordner die Tests zu diesem Projekt.

All diese Ordner sind alle nicht Pflicht und du musst auch nicht alle so benutzen. Beispielsweise könntest du die config/ und database/ Ordner auch ins src/ Verzeichnis schieben, oder gar auf das src/ komplett verzichten. Das ist komplett dir überlassen.

Wenn du vorhast das Package zu veröffentlichen und Open-Source zu stellen, dann solltest du vielleicht darauf achten, dass der Code nicht durcheinander ist und man sich schnell und gut zurecht findet.

2. Schritt: composer init

Jetzt wo wir die Struktur geklärt haben, können wir loslegen das Grundgerüst eines Laravel Packages umzusetzen. Als Erstes brauchen wir eine composer.json Datei. In dieser werden nicht nur Meta-Daten vom Package festgehalten, sondern auch definiert was der Autoloader machen soll, wenn das Package in einem Projekt installiert wird.

Führe composer init in deinem neuen Verzeichnis aus und folge den Anweisungen. Beim Namen kannst du wie bereits oben erklärt deinen Vendor-Namen und Package-Namen benutzen. In meinem Fall wäre das aheenam/awesome.

Arbeite dich durch die Fragen bis du bei den Anforderungen ankommst. Es gibt mindestens zwei dependencies, die du direkt zu Anfang schon definieren kannst:

  1. illuminate/support brauchst du für den ServiceProvider
  2. php um festzulegen, unter welcher PHP-Version du arbeitest.

Wenn du dein Package auch testen willst, dann werden auch diese Packages für dich interessant sein.

  1. phpunit/phpunit
  2. orchestra/testbench

Bei der Version musst du nun überlegen, welches Version du unterstützen willst. In unserem Fall würdest du bei illuminate/support als Version 5.5 eintragen. Du kannst aber auch, wenn du mehrere Versionen unterstützen willst 5.3|5.4|5.5 usw. eintragen. Genau Informationen wie du Versionen edfinierst findest du auf der entsprechenden Seite von Composer.

Am Ende solltest du eine composer.json Datei in deinem Projekt haben.

Meine Datei sieht so aus:

{
  "name": "aheenam/awesome",
  "description": "An awesome Laravel Package",
  "license": "MIT",
  "authors": [],
  "minimum-stability": "stable",
  "require": {
    "php": "^7.0",
    "illuminate/support": "~5.5.0"
  },
  "require-dev": {
    "phpunit/phpunit": "5.*|^6.3",
    "orchestra/testbench": "~3.4.0|~3.5.0"
  }
}

Wenn du nun

$ composer install

ausführst, werden die Packages im vendor Ordner installiert.

Autoloading

Wenn jemand dein Package in seinem Laravel Projekt installiert, schaut composer in dieser generierten composer.json nach, ob irgendetwas in den Autoloader muss oder nicht. Diese Defition fehlt bei uns noch. Füge also noch je nach dem wie deine Ordner-Struktur aussieht die entsprechenden Regeln hinzu.

"autoload": {
    "psr-4": {
        "Aheenam\\Awesome\\": "src"
    }
},
"autoload-dev": {
    "psr-4": {
        "Aheenam\\Awesome\\Test\\": "tests"
    }
},

Bei mir wird ganz einfach alles mit Aheenam\Awesome in den src/ Ordner gemappt und alles, wo noch ein Test dran ist, in den tests/ Ordner. Die anderen Ordner config und database lasse ich hier weg.

Package Discovery

In früheren Versionen von Laravel musste man den ServiceProvider eines Packages immer in seiner app.php Config-Datei eintragen, damit die Laravel Instanz das Package erkannt hat. Seit Laravel 5.5 gibt es das Feature "Package Discovery", welches dafür sorgt, dass der ServiceProvider automatisch erkannt wird.

Da Laravel aber dem Entwickler keine Vorgaben macht wie man sein Package zu strukturieren hat, muss man dem Feature irgendwie eine Information mitgeben wo der ServiceProvider liegt. Dies erfolgt auch in der composer.json.

"extra": {
    "laravel": {
        "providers": [
            "Aheenam\\Awesome\\AwesomeServiceProvider"
        ]
    }
},

Mit diesem kleinen Eintrag sorgst du dafür, dass der Benutzer deines Package bei sich einfach nur ein composer require aheenam/awesome ausführen muss und Laravel dann erkennt, dass unter dem Namespace Aheenam\\Awesome\\AwesomeServiceProvider sich ein ServiceProvider befindet.

3. Schritt: Der ServiceProvider

Im nächsten Schritt müssen wir natürlich auch den ServiceProvider erstellen, den wir bereits definiert haben. Dieser liegt bei mir direkt im src/ Verzeichnis und trägt den Namen des Packages: Der ServiceProvider für das Package Awesome lautet also AwesomeServiceProvider.

So sieht die Grundstruktur für diesen Provider aus:

<?php

namespace Aheenam\Awesome;

use Illuminate\Support\ServiceProvider;

class AwesomeServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = false;

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
    }


    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
    }
}

Dieser Service Provider ist quasi das Grundgerüst jedes Packages und ist für das Registrieren und Laden bestimmter Elemente eines Laravel-Pakets zuständig. Selbst der Laravel-Core wird über Service Provider geladen. In der offiziellen Dokumentation findest du mehr Informationen über die Service Provider.

Erstmal lassen wir diesen so wie er ist. Sobald wir anfangen zu Entwickeln, werden wir diesen wieder benötigen.

4. Schritt: Testing

Als letzen Schritt bevor wir anfangen unser Package zu entwickeln kommt das Thema Testing. Dieser Schritt ist nicht Pflicht, doch rate ich jedem Entwickler Tests zu schreiben. Gerade dann, wenn das Projekt mehr als nur ein Prototyp ist und nicht nur auf deinem heimischen Rechner installiert wird.

Für das Testen benutze ich PHPUnit. Dies kannst du mit einer XML Datei konfigurieren.

Erstelle dafür eine phpunit.xml im Root-Verzeichnis deines Projektes und trage folgendes ein:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         verbose="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false">
    <testsuites>
        <testsuite name="Aheenam Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory>src/</directory>
        </whitelist>
    </filter>
</phpunit>

Genaue Details zu weiteren Konfigurationsmöglichkeiten findest du in der Dokumentation von PHPUnit

Testbench

Bei einfachen PHP Packages könnten wir den TestCase von PHPUnit benutzen, um unsere Tests zu schreiben. In unserem Fall wollen wir aber testen, ob unser Package in einer Laravel Umgebung funktioniert. Diese haben wir aber in unserem Package nicht.

Diesen Problem begegnet Testbench. Wir legen eine TestCase.php in unserem Ordner tests/ an.

<?php

namespace Aheenam\Awesome\Test;

use Aheenam\Awesome\AwesomeServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;

abstract class TestCase extends Orchestra
{

    /**
     * Setup the test environment.
     */
    public function setUp()
    {
        parent::setUp();
    }

    /**
     * add the package provider
     *
     * @param $app
     * @return array
     */
    protected function getPackageProviders($app)
    {
        return [AwesomeServiceProvider::class];
    }

    /**
     * Define environment setup.
     *
     * @param  \Illuminate\Foundation\Application  $app
     * @return void
     */
    protected function getEnvironmentSetUp($app)
    {
        // Setup default database to use sqlite :memory:
        $app['config']->set('database.default', 'testing');
        $app['config']->set('database.connections.testing', [
            'driver'   => 'sqlite',
            'database' => ':memory:',
            'prefix'   => '',
        ]);
    }
}

In diesem TestCase können wir nun den ServiceProvider unseres Projektes hinterlegen, Änderungen an den Configs eines Laravel Projektes machen und weitere Einstellungen vornehmen. Details dazu findest du auf der GitHub-Seite von Testbench

Nun müssen wir nur noch darauf achten, dass wir bei jedem Test auch diesen TestCase benutzen.

5. Schritt: Controllers & Routes

Nun können wir anfangen unseren ersten Code zu erstellen. Unser triviales "Hello World"-Programm soll nichts anderes machen als beim Aufruf einer bestimmten Route einfach den Namen, der in der Route enthalten ist zu begrüßen.

Das brauchen wir als erstes die Route. Wie auch in der Laravel App erstellen wir uns einen Ordner routes/ und darin eine web.php:

$ touch src/routes/web.php

Darin kannst du dann so arbeiten wie du es unter Laravel kennst. Du musst nur dabei bedenken, dass du wieder ein neues Package in deine dependencies aufnehmen musst: illuminate/routing

<?php

Route::get('/test/{name}')
    ->uses('Aheenam\Awesome\Controllers\TestController@index');

Mit einem GET-Request auf diese Route können wir also nun einen Namen festlegen, welchen wir dann später für die Ausgabe benutzen können. Diese Route führt zum TestController, in welcher dann die Methode index() aufgerufen werden soll.

Beachte, dass du hier den ganzen Namespace des Controllers hinterlegen musst.

Erstellen wir nun den Controller

$ touch src/Controllers/TestController.php

Im TestController arbeitest du so wie du auch im normalen Projekt arbeiten würdest, mit der einzigen Ausnahme, dass wir direkt den Basis Controller aus dem illuminate/routing package erweitern anstelle des Controllers aus dem app/ Ordner im Projekt.

<?php

namespace Aheenam\Test\Controllers;

use Illuminate\Routing\Controller;

class TestController extends Controller
{

    /**
     * @param $name
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function index($name)
    {
        return view('test::index', [
            'name' => $name
        ]);
    }

}

Hier sehen wir, dass wieder etwas benutzt wird, welches eine neue Dependecy benötigt: illuminate/view. Dies muss dann also auch in die composer.json

Routen registrieren

Als nächstes werden wir Laravel mitteilen, dass es auch unsere neuen Routen aus dem Package verwenden soll. Hier kommt nun der ServiceProvider ins Spiel. Der ServiceProvider hat zwei Funktionen boot() und register().

Mit der boot Methode kannst du nun unsere Routen laden lassen:

/**
 * Bootstrap the application services.
 *
 * @return void
 */
public function boot()
{
    $this->loadRoutesFrom(__DIR__.'/routes/web.php');
}

6. Schritt: Die View

Nun können wir also die Route aufrufen und das, was im Controller steht wird nun auch ausgeführt. Was steht in diesem? Wir übergeben die Variable $name aus der Route direkt an die View weiter. Diese heißt in diesem Fall test::index. Wir sehen hier, dass die View einen Präfix test:: hat. Dies signalisiert, dass nicht im normalen View-Fenster geschaut werden soll, sondern in den View-Ordner, der über einen Service Provider definiert wurde. Diese Definition haben wir noch nicht gemacht. Holen wir dies also nach.

Erstellen wir erst einmal einen Ordner innerhalb unseres src/ mit dem Namen views/ und befüllen diese direkt mit der index.blade.php, die wir später benutzen werden. Als nächstes wechseln wir wieder zu unserem Service Provider. Dort legen wir in der boot Methode fest, von welchem Ordner die Views geladen werden sollen.

/**
 * Register the application services.
 *
 * @return void
 */
public function register()
{
    $this->loadViewsFrom(__DIR__.'/views', 'test');
}

Wir legen fest, dass der Ordner views/ für die Views zuständig ist. Wir definieren im zweiten Parameter auch direkt den Namespace dafür, welchen wir bereit vorher als Präfix beim Laden der View benutzt haben.

Für weiterführende Informationen schaue auf der entsprechenden Seite in der Laravel Dokumentation nach. Dort findest du auch Informationen wie du Config-Files und Translations lädst und auch wie du bestimmte Assets zum Publishen freigibst.

Veröffentlichen des Laravel Packages

Mit diesen einfachen 6 Schritten hast du dein erstes Paket erstellt. Nun könntest du auf die Idee kommen, dass dein Package auch für andere interessant sein könnte. Dann solletst du es anderen über Packagist anderen zur Verfügung stellen.

Vorher gibt es aber noch Kleinigkeiten vorzubereiten:

  1. README: Du solltest dem Benutzer deines Package eine verständliche Dokumentation deines Packages zur Verfügung stellen. Informationen wie man das Package installiert und benutzt solltest du in einer README.md festhalten
  2. Contribution Guide: Ob und wie andere Entwickler mit an deinem Package arbeiten können, solltest du in einer CONTRIBUTION.md definieren
  3. LICENSE: Welche Lizenz soll dein OpenSource Package haben? Lege die Regel für dein Package in einer LICENSE Datei fest. Eine gute Anlaufstelle hier ist Choose an open source license

Wenn du diese Vorbereitungen alle getroffen hast, kannst du dein Package über Packagist veröffentlichen. Das geht einfach indem du dir einen Account dort erstellst und ein kleines Formular deine GitHub URL deines Packages dort einreichst.

Laravel Package Generator

Jedesmal, wenn du ein Laravel Packages neu erstellst, wirst du die ersten oben genannten Schritte immer wieder durchführen. Um sich diese Schritte zu sparen, habe ich einen kleinen Generator geschrieben, der einem die Arbeit an dieser Stelle abnehmen soll.

Installiere das composer package global mit

$ composer global require aheenam/laravel-package-cli

Nun kannst du mit

$ laravel-package generate vendor/package-name

dir das Grundgerüst generieren lassen. Für mehr Einstellungsmöglichkeiten, schau dir die GitHub Seite vom Laravel Package CLI an. Falls du Features vermisst, oder generell Hilfe brauchst, benutze die Issue, um mich zu kontaktieren.