Salta al contenuto principale

Sviluppare un Package per Laravel

Profile picture for user luca77king

In questa lezione affronteremo il discorso della pacchettazione delle funzionalità nel contesto di un’applicazione Laravel. Alla fine di questo post, il lettore dovrebbe acquisire le competenze necessarie per sviluppare packages per Laravel.

Laravel è un ottimo framework per lo sviluppo di PHP e uno degli aspetti che lo rende così popolare è la disponibilità di una gran quantità di packages. I pacchetti possono essere utilizzati per aggiungere funzionalità extra alle applicazioni e in linea di massima sono disponibili packages per quasi tutto ciò che può immaginare.

Tuttavia, a volte può presentarsi la situazione in cui abbiamo bisogno di creare un package, magari per installare una data funzionalità su più di un progetto.

Detto ciò, vediamo come procedere con lo sviluppo. Il primo step è creare una cartella all’interno della nostra applicazione che chiameremo packages. Questa cartella sarà la nostra “officina” per lo sviluppo del packages e non va assolutamente inclusa nel progetto. Infatti la cartella va dichiarata nel file .gitignore.

All’interno di questa cartella ne creiamo un’altra e la rinominiamo con il nome con cui vogliamo essere identificati in qualità di vendor, nel mio caso è lucaterribili. Dopo di che, all’interno della cartella del vendor ne creiamo un’altra che avrà il nome del nostro package, ad esempio helloworld. Infine, dentro quest’ultima directory creiamo la cartella src che conterrà le classi del nostro nuovo package.

Struttura package

Creazione dei files indispensabili per il packages

I files indispensabili del nostro nuovo package sono il ServiceProvider e il composer.json. Come abbiamo imparato nel capitolo dedicato, il ServiceProvider è la classe per fornire i servizi e gestire tutte le dipendenze all'interno del package.

Il ServiceProvider del package sarà incluso nell’applicazione attraverso Composer. Dobbiamo infatti creare un file composer.json all’interno della cartella helloworld e modificare il composer.json nella root del progetto, in modo che Composer carichi tutto correttamente. Ma andiamo per steps…. In primo luogo creiamo il ServiceProvider da collocare nella cartella src.

<?php
namespace LucaTerribili\HelloWorld;
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
class ServiceProvider extends LaravelServiceProvider
{
    public function register()
    {
        //
    }
    public function boot()
    {
        //
    }
}

Nella classe abbiamo definito un namespace che attraverso il composer.json del package andremo a definire nell’autoload secondo lo standard PSR-4

{
    "name": "lucaterribili/helloworld",
    "type": "laravel-library",
    "description": "Test Package",
    "minimum-stability": "stable",
    "license": "proprietary",
    "authors": [
    {
        "name": "Luca Terribili",
        "email": "info@lucaterribili.it"
    }
    ],
    "require": {
        "php": ">=8.0",
        "composer/installers": "v2.1.1"
    },
    "require-dev": {
        "orchestra/testbench": "^7.0"
    },
    "autoload": {
        "psr-4": {
            "LucaTerribili\\HelloWorld\\": "src/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
            "LucaTerribili\\HelloWorld\\ServiceProvider"
            ]
        }
    },
    "config": {
        "allow-plugins": {
            "composer/installers": true
        }
    }
}

Adesso abbiamo le informazioni minime per il corretto funzionamento del packages. Ora dobbiamo iniettare il package all'interno dell'applicazione, ma siccome siamo in fase di sviluppo e il package non è disponibile in alcun repository, lo collegheremo attraverso un link simbolico nel composer.json principale, situato nella root del progetto.

Prima dell’istruzione require inseriamo questo blocco per immettere repositories aggiuntivi, dove indicheremo a Composer che vogliamo caricare il nostro package.

"repositories": [
{
    "type": "path",
    "url": "packages/lucaterribili/helloworld",
    "options": {
        "symlink": true
    }
}
],

E nella lista del blocco require inseriamo il nostro package, impostando come versione un asterisco *

"lucaterribili/helloworld": "*"

Lanciamo il comando composer update e aspettiamo il completamento dell’operazione

Ora il nostro package è attivo e funzionante. Nei passaggi seguenti andremo a vedere alcune operazioni che possiamo gestire attraverso il pacchetto e infine vedremo il processo di pubblicazione dello stesso.

Caricare le impostazioni di configurazione

Tutti i package hanno delle impostazioni di configurazione che l'utente può gestire a suo piacimento. Per poter essere modificate dall’utente finale, le impostazioni devono essere pubblicate al di fuori del package. Ma allo stesso tempo dobbiamo assicurarci che il package funzioni correttamente anche se gli utenti si dimenticano di esportare le configurazioni.

All’interno della cartella src, creiamo una sottocartella che chiameremo config e all’interno di quest’ultima creiamo il file di configurazione helloworld.php. Al suo interno metteremo solo una voce, giusto per apprendere il concetto.

<?php
return [
'use_package_route' => true
];
Andiamo sul ServiceProvider del package e inseriamo nel metodo register() la dichiarazione per caricare la configurazione che abbiamo appena creato.
public function register()
{
    $this->mergeConfigFrom(
    __DIR__ . '/config/helloworld.php',
    'helloworld'
    );
}

Nel metodo boot(), invece, faremo in modo che la configurazione sia esportata nella cartella config di Laravel quando verrà lanciato l’apposito comando da terminale

public function boot()
{
    $this->publishes([
    __DIR__ . '/config/helloworld.php' => config_path('helloworld.php'),
    ], 'helloworld');
}

Proviamo a testare se il processo funziona: Digitiamo sulla console questo comando

php artisan vendor:publish --tag=helloworld

Dovremmo ricevere questo output di avvenuto successo

Il nostro package prende forma! Adesso vediamo come caricare delle rotte aggiuntive attraverso il ServiceProvider.

Caricare delle rotte aggiuntive

Abbiamo visto nel capitolo dedicato alla gestione delle richieste HTTP che rotte e controller vanno in coppia. Perciò, all'interno del nostro package, dovremo creare sia le rotte che i relativi Controllers. Seguite il diagramma di seguito per impostare la struttura dei files.

Cominciamo a lavorare sul Controller, impostando un metodo index() che in un primo momento restituirà solo un messaggio a video.

<?php
namespace LucaTerribili\HelloWorld\Http\Controllers;
use Illuminate\Routing\Controller;
class HelloWorldController extends Controller
{
    public function index()
    {
        echo 'Hello World';
    }
}

Adesso implementiamo il file web.php all'interno della cartella routes. Abbiamo già imparato a gestire le rotte, per cui mi limiterò a postare il codice senza dilungarmi in chiacchiere.

<?php
use LucaTerribili\HelloWorld\Http\Controllers\HelloWorldController;
Route::controller(HelloWorldController::class)->prefix('helloworld')->as('helloworld.')->group(callback: function (
) {
    Route::get('/', 'index')->name('index');
});

Ultimo step, carichiamo le rotte attraverso il metodo boot() del ServiceProvider, a condizione che il relativo parametro di configurazione sia true.

public function boot()
{
    $this->publishes([
    __DIR__ . '/config/helloworld.php' => config_path('helloworld.php'),
    ], 'helloworld');
    // Carichiamo le rotte se necessario
    if (config('helloworld.use_package_route')) {
        $this->loadRoutesFrom(__DIR__ . '/routes/web.php');
    }
}

Lanciamo il comando php artisan optimize e verifichiamo se all’url /helloworld risponde il nostro Controller.

Uso ed esportazione delle viste

Siamo quasi arrivati alla fine dello sviluppo del nostro package. Abbiamo creato le configurazioni, abbiamo creato un controller, non ci rimane che creare una vista.

Le viste di un package, per essere identificate da Laravel, devono avere un prefisso univoco che le colleghi correttamente al package. Vediamo come fare: creiamo una nuova cartella dove collocheremo i nostri Blade, seguendo il diagramma di seguito.

Definiamo nel metodo boot() del ServiceProvider il percorso delle viste e identifichiamole con un gruppo univoco, ovviamente helloworld.

public function boot()
{
    $this->publishes([
    __DIR__ . '/config/helloworld.php' => config_path('helloworld.php'),
    ], 'helloworld');
    // Carichiamo le rotte se necessario
    if (config('helloworld.use_package_route')) {
        $this->loadRoutesFrom(__DIR__ . '/routes/web.php');
    }
    // Carichiamo le viste che avranno come namespace helloworld
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'helloworld');
}

Ora modifichiamo il metodo index() del Controller in modo che restituisca la vista main che abbiamo collocato nella cartella views del package

public function index()
{
    return view('helloworld::main');
}

Inserite qualsiasi cosa nel Blade per testare se tutto funziona correttamente, dopo di che vediamo il metodo per esportare le viste del package. Infatti, come per le configurazioni, dobbiamo dare agli altri sviluppatori la possibilità di personalizzare il template.

Sempre all’interno del metodo boot() inseriamo questa dichiarazione.

// Pubblichiamo le viste
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/helloworld')
], 'helloworld');

Lanciamo nuovamente il comando per esportare le risorse del package

php artisan vendor:publish --tag=helloworld

Il sistema copierà tutto quello che trova all’interno della cartella packages/lucaterribili/helloworld/resources/views all’interno della cartella resources/views/vendor/helloworld. Da questo momento, i blade esportati avranno la priorità nella visualizzazione. Perciò eventuali modifiche andranno fatte al di fuori del package.

Fate attenzione che ogni volta che esporteremo risorse fuori dal package, perderemo le modifiche che abbiamo apportato alle viste dentro la cartella resources/views/vendor

Versionare il package

Iniziamo la procedura per pubblicare il package. Abbiamo bisogno di un account su Github, se non lo avete, fatelo subito perché è indispensabile per il lavoro da sviluppatore.

Una volta effettuato il login, creiamo un nuovo repository che ovviamente dovrà essere pubblico

Una volta completata questa prima parte della procedura, GitHub ci fornirà le istruzioni necessarie per pushare il package che abbiamo appena creato nel nuovo repository.

Ovviamente dobbiamo pushare solo il package e non l'intero progetto Laravel, quindi le istruzioni fornite da GitHub devono essere eseguite con il terminale aperto nella radice del package

Torniamo su GitHub e individuiamo il pulsante per creare una nuova release. Nella schermata successiva impostiamo la prima release in questo modo

Premiamo Salva e su GitHub abbiamo finito!

Pubblicare il package

Per completare il lavoro, abbiamo bisogno di pubblicare il package in un altro sito. Si tratta di , il repository ufficiale per i package PHP.

Per registrarci possiamo utilizzare il SSO di GitHub. Allo stesso modo possiamo pubblicare i packages che abbiamo già condiviso su GitHub con un semplice click.

Clicchiamo su Submit e in pochi secondi il package verrà pubblicato

Ora torniamo sul nostro progetto Laravel e nel composer.json (nella root del progetto) eliminiamo il blocco dei repositories aggiuntivi e modifichiamo l'import del nostro package, impostando la versione.

"lucaterribili/helloworld": "^1.0"

Lanciamo il comando composer update e la cli dovrebbe restituire questi output