> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Sviluppo di pacchetti Laravel

> Come sviluppare pacchetti Laravel imperniati sui service provider: dalla pubblicazione di configurazioni, view, migrazioni e facade all'auto-discovery e al caricamento differito.

## Cos'è un pacchetto

In Laravel un pacchetto è un pacchetto Composer che aggiunge funzionalità all'applicazione. Ne esistono principalmente due tipi.

* **Pacchetto standalone** — libreria PHP generica non dipendente da Laravel (es. Carbon, Pest)
* **Pacchetto Laravel** — pacchetto integrato con Laravel che offre rotte, controller, view, configurazione, ecc.

Questa guida tratta il secondo caso: lo sviluppo di pacchetti specifici per Laravel. Serve una conoscenza approfondita della struttura interna di Laravel — service provider, facade, pubblicazione della configurazione.

<Info>
  Per scrivere i test di un pacchetto usi [Orchestra Testbench](https://github.com/orchestral/testbench). Ti permette di scrivere i test del pacchetto come faresti in una normale applicazione Laravel.
</Info>

## Auto-discovery dei pacchetti

Quando installi un pacchetto, Laravel legge la sezione `extra.laravel` del `composer.json` e registra automaticamente service provider e facade.

```json theme={null}
"extra": {
    "laravel": {
        "providers": [
            "Acme\\Courier\\CourierServiceProvider"
        ],
        "aliases": {
            "Courier": "Acme\\Courier\\Facades\\Courier"
        }
    }
}
```

Con questa configurazione, l'utente non deve modificare manualmente `bootstrap/providers.php`: il pacchetto viene caricato automaticamente.

### Disabilitare l'auto-discovery

Se l'utente vuole disabilitare l'auto-discovery per un pacchetto specifico, imposta il `composer.json` dell'applicazione.

```json theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

## Ruolo del service provider

Il service provider è il punto di ingresso del pacchetto. Concentra qui la registrazione delle risorse — view, config, migrazioni, rotte — in Laravel.

Estende `Illuminate\Support\ServiceProvider` e ha due metodi: `register` e `boot`.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    /**
     * Registra i servizi del pacchetto
     */
    public function register(): void
    {
        // qui esegui il binding nel service container
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );

        $this->app->singleton(CourierManager::class, function ($app) {
            return new CourierManager($app['config']['courier']);
        });
    }

    /**
     * Bootstrap dei servizi del pacchetto
     */
    public function boot(): void
    {
        // qui registri le risorse
        $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
        $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

        $this->publishesMigrations([
            __DIR__.'/../database/migrations' => database_path('migrations'),
        ]);

        $this->publishes([
            __DIR__.'/../config/courier.php' => config_path('courier.php'),
        ], 'courier-config');

        $this->publishes([
            __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
        ], 'courier-views');
    }
}
```

<Warning>
  In `register` non registrare listener di eventi, rotte o view. Rischi di usare per errore servizi di un altro service provider non ancora caricato. Le operazioni diverse dai binding vanno sempre in `boot`.
</Warning>

## Pubblicare i file di configurazione

### publishes() — pubblicare i file

Chiamando `publishes()` in `boot`, l'utente può copiare i file di configurazione nella propria app con `vendor:publish`.

```php theme={null}
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ]);
}
```

Dopo la pubblicazione, i valori si leggono con la normale API config.

```php theme={null}
$value = config('courier.option');
```

### mergeConfigFrom() — fondere con i valori di default

Con `mergeConfigFrom()` in `register`, i default del pacchetto si applicano anche se l'utente non ha pubblicato il file.

```php theme={null}
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}
```

<Warning>
  `mergeConfigFrom()` non fonde a livelli profondi degli array annidati. In configurazioni con array multidimensionali, se l'utente definisce solo una parte, il resto delle opzioni potrebbe non essere fuso.
</Warning>

### Separare i gruppi di pubblicazione con i tag

Passando un tag come secondo argomento di `publishes()`, l'utente pubblica solo le risorse desiderate.

```php theme={null}
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ], 'courier-config');

    $this->publishesMigrations([
        __DIR__.'/../database/migrations/' => database_path('migrations'),
    ], 'courier-migrations');
}
```

```shell theme={null}
# pubblica solo i file di configurazione
php artisan vendor:publish --tag=courier-config

# pubblica tutte le risorse offerte dal provider
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider"
```

## Registrare rotte

Carica i file delle rotte con `loadRoutesFrom()`. Se la cache delle rotte dell'app è attiva, viene automaticamente saltato.

```php theme={null}
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
```

Nel file delle rotte indichi i controller del pacchetto.

```php theme={null}
// routes/web.php
use Acme\Courier\Http\Controllers\TrackingController;
use Illuminate\Support\Facades\Route;

Route::prefix('courier')->group(function () {
    Route::get('/track/{id}', [TrackingController::class, 'show'])
        ->name('courier.track');
});
```

## Pubblicare le migrazioni

Con `publishesMigrations()` puoi pubblicare i file di migrazione. Al momento della pubblicazione Laravel aggiorna automaticamente il timestamp.

```php theme={null}
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}
```

## Pubblicare le view

### loadViewsFrom() — registrare le view

Con `loadViewsFrom()` registri la directory delle view. Usa il namespace del secondo argomento per riferirti alle view come `package::view`.

```php theme={null}
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}
```

Dopo la registrazione, riferisci le view con il namespace del pacchetto.

```php theme={null}
Route::get('/dashboard', function () {
    return view('courier::dashboard');
});
```

Laravel cerca le view in due posti: prima in `resources/views/vendor/courier` dell'app, poi nella directory view del pacchetto. Questo permette all'utente di personalizzarle.

### Pubblicare le view

```php theme={null}
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

    $this->publishes([
        __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
    ], 'courier-views');
}
```

### Registrare componenti Blade

Se includi componenti nel pacchetto li registri in `boot`.

```php theme={null}
use Illuminate\Support\Facades\Blade;
use Acme\Courier\View\Components\AlertComponent;

public function boot(): void
{
    Blade::component('courier-alert', AlertComponent::class);
}
```

Puoi anche registrarli in blocco con un namespace di componenti.

```php theme={null}
use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    Blade::componentNamespace('Acme\\Courier\\View\\Components', 'courier');
}
```

```blade theme={null}
{{-- registrazione singola --}}
<x-courier-alert />

{{-- registrazione con namespace --}}
<x-courier::alert />
```

## Pubblicare i file di traduzione

Registri i file di traduzione con `loadTranslationsFrom()`. Le traduzioni si referenziano nella forma `package::file.key`.

```php theme={null}
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

    $this->publishes([
        __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
    ]);
}
```

```php theme={null}
// uso della traduzione
echo trans('courier::messages.welcome');
```

Per file di traduzione JSON usi `loadJsonTranslationsFrom()`.

```php theme={null}
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
```

## Registrare comandi

I comandi Artisan del pacchetto si registrano con il metodo `commands()`. Di solito solo in ambiente console.

```php theme={null}
use Acme\Courier\Console\Commands\InstallCommand;
use Acme\Courier\Console\Commands\SyncCommand;

public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            InstallCommand::class,
            SyncCommand::class,
        ]);
    }
}
```

### Integrazione con il comando optimize

Se il pacchetto ha una sua cache, con il metodo `optimizes()` puoi integrarti con `php artisan optimize` e `php artisan optimize:clear`.

```php theme={null}
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'courier:cache',
            clear: 'courier:clear-cache',
        );
    }
}
```

### Aggiungere informazioni al comando `about`

Per aggiungere informazioni del pacchetto all'output di `php artisan about` usa `AboutCommand::add()`.

```php theme={null}
use Illuminate\Foundation\Console\AboutCommand;

public function boot(): void
{
    AboutCommand::add('Courier Package', fn () => ['Version' => '1.0.0']);
}
```

## Creare un facade

Un facade permette di richiamare i binding del service container come metodi statici.

<Steps>
  <Step title="Creare la classe di servizio">
    ```php theme={null}
    <?php

    namespace Acme\Courier;

    class CourierManager
    {
        public function __construct(
            protected array $config,
        ) {}

        public function send(string $to, string $message): bool
        {
            // logica di invio messaggio
            return true;
        }

        public function track(string $id): array
        {
            // logica di recupero delle informazioni di tracking
            return ['status' => 'delivered'];
        }
    }
    ```
  </Step>

  <Step title="Creare la classe facade">
    Estendi `Illuminate\Support\Facades\Facade` e restituisci in `getFacadeAccessor()` la chiave di binding del container.

    ```php theme={null}
    <?php

    namespace Acme\Courier\Facades;

    use Illuminate\Support\Facades\Facade;

    /**
     * @method static bool send(string $to, string $message)
     * @method static array track(string $id)
     *
     * @see \Acme\Courier\CourierManager
     */
    class Courier extends Facade
    {
        protected static function getFacadeAccessor(): string
        {
            return \Acme\Courier\CourierManager::class;
        }
    }
    ```
  </Step>

  <Step title="Fai il binding nel service provider">
    ```php theme={null}
    public function register(): void
    {
        $this->app->singleton(\Acme\Courier\CourierManager::class, function ($app) {
            return new \Acme\Courier\CourierManager($app['config']['courier']);
        });
    }
    ```
  </Step>

  <Step title="Registrare in composer.json">
    ```json theme={null}
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    }
    ```
  </Step>
</Steps>

Aggiungendo le annotazioni PHPDoc `@method` ai metodi del facade abiliti l'autocompletamento dell'IDE.

```php theme={null}
// invocazione del servizio tramite facade
use Acme\Courier\Facades\Courier;

Courier::send('user@example.com', 'Il tuo pacco è arrivato');
$status = Courier::track('ABC-123');
```

## DeferrableProvider — implementare il caricamento differito

I provider che eseguono solo binding nel container possono realizzare il caricamento differito implementando l'interfaccia `DeferrableProvider`. Il provider non viene caricato finché il servizio non è effettivamente necessario: le prestazioni dell'app migliorano.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(CourierManager::class, function ($app) {
            return new CourierManager($app['config']['courier']);
        });
    }

    /**
     * Restituisce la lista dei servizi forniti da questo provider
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [CourierManager::class];
    }
}
```

Laravel compila e memorizza la lista dei servizi forniti dai deferred provider. Il provider viene caricato solo quando uno dei servizi elencati in `provides()` viene risolto.

<Warning>
  Non usare `DeferrableProvider` in provider che devono registrare risorse (view, rotte, listener di eventi, ecc.). Con il caricamento differito, quelle risorse resterebbero non registrate.
</Warning>

## Test del pacchetto

Per testare il pacchetto in isolamento usa [Orchestra Testbench](https://github.com/orchestral/testbench). Ti permette di scrivere i test come se fossi in un'app Laravel.

```shell theme={null}
composer require --dev orchestra/testbench
```

Nel test case fai override di `getPackageProviders()` per registrare il service provider del pacchetto.

```php theme={null}
<?php

namespace Acme\Courier\Tests;

use Acme\Courier\CourierServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;

class TestCase extends BaseTestCase
{
    /**
     * Registra i service provider del pacchetto
     */
    protected function getPackageProviders($app): array
    {
        return [
            CourierServiceProvider::class,
        ];
    }

    /**
     * Registra gli alias dei facade del pacchetto
     */
    protected function getPackageAliases($app): array
    {
        return [
            'Courier' => \Acme\Courier\Facades\Courier::class,
        ];
    }

    /**
     * Configurazione d'ambiente per i test
     */
    protected function defineEnvironment($app): void
    {
        $app['config']->set('courier.api_key', 'test-key');
    }
}
```

```php theme={null}
<?php

namespace Acme\Courier\Tests\Feature;

use Acme\Courier\Facades\Courier;
use Acme\Courier\Tests\TestCase;

class CourierTest extends TestCase
{
    public function test_can_send_message(): void
    {
        $result = Courier::send('user@example.com', 'messaggio di test');

        $this->assertTrue($result);
    }
}
```

## Pubblicazione su Composer

Best practice per pubblicare il pacchetto su [Packagist](https://packagist.org/).

**Impostazioni base di `composer.json`**

```json theme={null}
{
    "name": "acme/courier",
    "description": "A Laravel courier package",
    "type": "library",
    "license": "MIT",
    "require": {
        "php": "^8.2",
        "illuminate/support": "^11.0||^12.0||^13.0"
    },
    "require-dev": {
        "orchestra/testbench": "^9.0||^10.0",
        "phpunit/phpunit": "^11.0"
    },
    "autoload": {
        "psr-4": {
            "Acme\\Courier\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Acme\\Courier\\Tests\\": "tests/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Acme\\Courier\\CourierServiceProvider"
            ],
            "aliases": {
                "Courier": "Acme\\Courier\\Facades\\Courier"
            }
        }
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}
```

<Tip>
  Dipendere da `illuminate/support` invece che da tutto `illuminate/framework` ti fa dipendere solo dai componenti necessari. Mantieni piccolo l'albero delle dipendenze del pacchetto.
</Tip>

**Esempio di struttura delle directory**

```
acme/courier/
├── config/
│   └── courier.php
├── database/
│   └── migrations/
│       └── 2024_01_01_000000_create_courier_logs_table.php
├── lang/
│   └── it/
│       └── messages.php
├── resources/
│   └── views/
│       └── dashboard.blade.php
├── routes/
│   └── web.php
├── src/
│   ├── Console/
│   │   └── Commands/
│   │       └── InstallCommand.php
│   ├── Facades/
│   │   └── Courier.php
│   ├── Http/
│   │   └── Controllers/
│   │       └── TrackingController.php
│   ├── CourierManager.php
│   └── CourierServiceProvider.php
├── tests/
│   ├── Feature/
│   └── TestCase.php
├── composer.json
└── README.md
```

## Pagine correlate

<Columns cols={2}>
  <Card title="Service provider" icon="plug" href="/it/service-providers">
    Rivedi i metodi `register` e `boot` dei service provider e i dettagli dei deferred provider.
  </Card>

  <Card title="Gestione della compatibilità tra versioni" icon="git-branch" href="/it/advanced/package-versioning">
    Strategie per adattarsi agli upgrade major di Laravel e PHP e configurazione della matrix di test in GitHub Actions.
  </Card>
</Columns>


## Related topics

- [Laravel Package Skeleton — template starter per pacchetti ufficiali](/it/blog/package-skeleton-introduction.md)
- [CHANGELOG e release management dei pacchetti](/it/advanced/package-changelog.md)
- [Gestire la compatibilità tra versioni dei pacchetti](/it/advanced/package-versioning.md)
- [Analisi statica dei pacchetti (PHPStan / Larastan)](/it/advanced/package-static-analysis.md)
- [GeneratorCommand — implementare comandi `make:` personalizzati](/it/advanced/generator-command.md)
