Créer un package Laravel réutilisable
Vous avez du code que vous réutilisez entre projets ? C'est le moment de le transformer en package. Ce guide couvre tout : structure, Service Provider, config, tests et publication.
Pourquoi créer un package ?
Un package permet de réutiliser du code entre projets, de le versionner indépendamment, et de le partager avec la communauté (ou juste votre équipe).
Cas d'usage typiques
Intégrations API : wrapper pour une API externe (Stripe, Mailchimp...)
Fonctionnalités communes : système d'audit, gestion de media, permissions
Composants UI : Blade components, Livewire components
Outils dev : debugging, profiling, helpers
Structure du package
Structure recommandée
my-package/
├── src/
│ ├── MyPackageServiceProvider.php
│ ├── Facades/
│ │ └── MyPackage.php
│ ├── Services/
│ │ └── MyPackageService.php
│ └── Http/
│ ├── Controllers/
│ └── Middleware/
├── config/
│ └── my-package.php
├── database/
│ └── migrations/
├── resources/
│ └── views/
├── routes/
│ └── web.php
├── tests/
│ ├── TestCase.php
│ └── Feature/
├── composer.json
├── README.md
└── LICENSE
Initialiser le package
1. Créer le composer.json
composer.json
{
"name": "vendor/my-package",
"description": "Description de mon package",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Votre Nom",
"email": "email@example.com"
}
],
"require": {
"php": "^8.2",
"illuminate/support": "^11.0|^12.0"
},
"require-dev": {
"orchestra/testbench": "^9.0",
"phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": {
"Vendor\\MyPackage\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Vendor\\MyPackage\\Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Vendor\\MyPackage\\MyPackageServiceProvider"
],
"aliases": {
"MyPackage": "Vendor\\MyPackage\\Facades\\MyPackage"
}
}
},
"minimum-stability": "stable"
}
Package Discovery
La section extra.laravel permet l'auto-discovery : Laravel enregistre automatiquement votre Service Provider à l'installation, sans modification de config/app.php.
Le Service Provider
Le Service Provider est le cœur de votre package. Il enregistre tout : bindings, config, routes, vues, migrations.
src/MyPackageServiceProvider.php
namespace Vendor\MyPackage;
use Illuminate\Support\ServiceProvider;
class MyPackageServiceProvider extends ServiceProvider
{
public function register(): void
{
// Merge config avec celle de l'app
$this->mergeConfigFrom(
__DIR__.'/../config/my-package.php',
'my-package'
);
// Bind le service principal
$this->app->singleton(MyPackageService::class, function ($app) {
return new MyPackageService(
config('my-package.api_key')
);
});
}
public function boot(): void
{
// Publier la config
$this->publishes([
__DIR__.'/../config/my-package.php' => config_path('my-package.php'),
], 'my-package-config');
// Charger les routes
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
// Charger les vues
$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-package');
// Publier les vues
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/my-package'),
], 'my-package-views');
// Charger les migrations
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
// Ou publier les migrations
$this->publishesMigrations([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'my-package-migrations');
}
}
mergeConfigFrom vs publishes
Différence importante
mergeConfigFrom() dans register() : charge les valeurs par défaut.
publishes() dans boot() : permet à l'utilisateur de personnaliser.
Utilisez les deux pour que les configs non publiées fonctionnent quand même.
Fichier de configuration
config/my-package.php
return [
/*
|--------------------------------------------------------------------------
| API Key
|--------------------------------------------------------------------------
|
| Your API key for the external service.
|
*/
'api_key' => env('MY_PACKAGE_API_KEY'),
/*
|--------------------------------------------------------------------------
| Cache Duration
|--------------------------------------------------------------------------
|
| How long to cache API responses (in minutes).
|
*/
'cache_duration' => env('MY_PACKAGE_CACHE_DURATION', 60),
/*
|--------------------------------------------------------------------------
| Route Prefix
|--------------------------------------------------------------------------
|
| The prefix for all package routes.
|
*/
'route_prefix' => 'my-package',
];
Accès depuis l'application :
Utilisation
$apiKey = config('my-package.api_key');
$duration = config('my-package.cache_duration');
Créer une Facade
Les facades offrent une syntaxe pratique pour accéder à votre service.
src/Facades/MyPackage.php
namespace Vendor\MyPackage\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @method static array getData()
* @method static void sendNotification(string $message)
*
* @see \Vendor\MyPackage\Services\MyPackageService
*/
class MyPackage extends Facade
{
protected static function getFacadeAccessor(): string
{
return \Vendor\MyPackage\Services\MyPackageService::class;
}
}
Utilisation dans l'app
use Vendor\MyPackage\Facades\MyPackage;
// Via la facade
$data = MyPackage::getData();
// Ou via injection
public function __construct(
private MyPackageService $service,
) {}
Tests du package
Utilisez orchestra/testbench pour tester votre package dans un environnement Laravel.
tests/TestCase.php
namespace Vendor\MyPackage\Tests;
use Orchestra\Testbench\TestCase as BaseTestCase;
use Vendor\MyPackage\MyPackageServiceProvider;
abstract class TestCase extends BaseTestCase
{
protected function getPackageProviders($app): array
{
return [
MyPackageServiceProvider::class,
];
}
protected function getPackageAliases($app): array
{
return [
'MyPackage' => \Vendor\MyPackage\Facades\MyPackage::class,
];
}
protected function defineEnvironment($app): void
{
$app['config']->set('my-package.api_key', 'test-key');
}
}
tests/Feature/MyPackageTest.php
namespace Vendor\MyPackage\Tests\Feature;
use Vendor\MyPackage\Tests\TestCase;
use Vendor\MyPackage\Facades\MyPackage;
class MyPackageTest extends TestCase
{
public function test_can_get_data(): void
{
$result = MyPackage::getData();
$this->assertIsArray($result);
}
public function test_config_is_loaded(): void
{
$this->assertEquals(
'test-key',
config('my-package.api_key')
);
}
}
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Package Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
Développement local
Pendant le développement, liez votre package à un projet Laravel pour le tester en conditions réelles.
composer.json de l'app Laravel
{
"repositories": [
{
"type": "path",
"url": "../my-package",
"options": {
"symlink": true
}
}
],
"require": {
"vendor/my-package": "*"
}
}
Terminal
composer update vendor/my-package
Symlink
Avec symlink: true, les modifications dans le package sont immédiatement visibles dans l'app. Pas besoin de re-composer à chaque changement.
Publication sur Packagist
1. Créer le repo GitHub
Terminal
git init
git add .
git commit -m "Initial commit"
git remote add origin git@github.com:vendor/my-package.git
git push -u origin main
2. Créer un tag de version
Terminal
git tag v1.0.0
git push --tags
3. Soumettre sur Packagist
- Créez un compte sur packagist.org
- Cliquez sur "Submit" et collez l'URL du repo GitHub
- Configurez le webhook GitHub pour les mises à jour automatiques
Ensuite, votre package est installable via :
Terminal
composer require vendor/my-package
Bonnes pratiques
Checklist package Laravel
- README complet : installation, configuration, exemples d'utilisation
- CHANGELOG : documentez les changements entre versions
- Tests : visez une bonne couverture, surtout sur les features critiques
- Type hints : utilisez les types PHP 8.x pour une meilleure DX
- PHPDoc : documentez les facades pour l'autocomplétion IDE
- Semantic versioning : MAJOR.MINOR.PATCH
- Support multi-version : Laravel 11 et 12 si possible
- CI/CD : GitHub Actions pour les tests automatiques
GitHub Actions pour les tests
.github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: [8.2, 8.3]
laravel: [11.*, 12.*]
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- name: Install dependencies
run: |
composer require "illuminate/support:${{ matrix.laravel }}" --no-update
composer update --prefer-dist --no-progress
- name: Run tests
run: vendor/bin/phpunit