Package Development and Ecosystem
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Laravel 13.x (13.34.0) on PHP 8.3 to 8.5, checked on 2026-10-05. A Laravel package is
a Composer package with a service provider. Laravel discovers the provider from composer.json, and the provider
registers bindings, configuration, views, commands and routes. Bookshelf’s ISBN code is pulled out into
bookshelf/isbn below, and the second half of the page lists the packages most applications choose from. Composer
packages in general, with versioning, autoloading and publishing to Packagist, are on
Composer and Packages.
A package
The package lives next to the application, in ../packages/isbn:
packages/isbn/
├── composer.json
├── config/isbn.php
├── resources/views/isbn.blade.php
├── src/Isbn.php validation, normalisation, ISBN-13 conversion, fixed-width formatting
├── src/IsbnServiceProvider.php
├── src/Facades/Isbn.php
├── src/View/IsbnComponent.php <x-isbn value="…" />
├── src/Console/CheckIsbnCommand.php php artisan isbn:check
├── tests/TestCase.php
└── tests/IsbnTest.php
Source: Laravel — Package Development
extra.laravel in composer.json is what discovery reads:
{
"name": "bookshelf/isbn",
"require": {
"php": "^8.3",
"illuminate/console": "^12.0|^13.0",
"illuminate/support": "^12.0|^13.0",
"illuminate/view": "^12.0|^13.0"
},
"require-dev": { "orchestra/testbench": "^11.3", "phpunit/phpunit": "^12.5" },
"autoload": { "psr-4": { "Bookshelf\\Isbn\\": "src/" } },
"extra": {
"laravel": {
"providers": ["Bookshelf\\Isbn\\IsbnServiceProvider"],
"aliases": { "Isbn": "Bookshelf\\Isbn\\Facades\\Isbn" }
}
}
}
Source: Laravel — Package Discovery
The service provider does the wiring:
<?php
// src/IsbnServiceProvider.php
namespace Bookshelf\Isbn;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class IsbnServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/isbn.php', 'isbn'); // the app's file overrides keys, not the whole array
$this->app->singleton(Isbn::class, fn ($app) => new Isbn($app['config']->get('isbn.separator')));
}
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'isbn'); // isbn::isbn, overridable in resources/views/vendor/isbn
Blade::component('isbn', View\IsbnComponent::class); // <x-isbn value="…" />
if ($this->app->runningInConsole()) {
$this->commands([Console\CheckIsbnCommand::class]);
$this->publishes([__DIR__.'/../config/isbn.php' => config_path('isbn.php')], 'isbn-config');
$this->publishes([__DIR__.'/../resources/views' => resource_path('views/vendor/isbn')], 'isbn-views');
}
if (class_exists(\Illuminate\Foundation\Console\AboutCommand::class)) { // laravel/framework, not illuminate/*
\Illuminate\Foundation\Console\AboutCommand::add('ISBN', fn () => ['Separator' => config('isbn.separator')]);
}
}
}
Source: Laravel — Service Providers and Resources
Configuration is merged in register(), so other providers can read it during boot. Views, components, commands
and publishing belong in boot(). Publishing is only needed in the console, which the runningInConsole() check
reflects.
The require section must match what the code uses. The first version required only illuminate/support, like many
packages. AboutCommand lives in Illuminate\Foundation, which exists only in laravel/framework, and the Blade
component needs illuminate/view. In a full application that does not show, because the framework provides everything,
but the package would break when installed with the illuminate/* components alone. The fix is to require the
illuminate/* components the code uses and to guard the Foundation-only call.
Installing it with a path repository
During development the application installs the package from the directory, as a symlink, so changes are live:
$ composer config repositories.isbn '{"type": "path", "url": "../packages/isbn", "options": {"symlink": true}}'
$ composer require bookshelf/isbn:@dev
- Installing bookshelf/isbn (dev-main): Symlinking from ../packages/isbn
bookshelf/isbn ................................................ DONE
$ php -r '$m = require "bootstrap/cache/packages.php"; echo json_encode($m["bookshelf/isbn"]);'
{"providers":["Bookshelf\\Isbn\\IsbnServiceProvider"],"aliases":{"Isbn":"Bookshelf\\Isbn\\Facades\\Isbn"}}
Source: Composer — Path repositories
Nothing was added to bootstrap/providers.php. Discovery wrote the provider into bootstrap/cache/packages.php,
next to the 28 other discovered packages. Then:
$ php artisan isbn:check 978-0-8070-8305-4 0807083054 978-0-8070-8305-5 0-441-17271-X
+-------------------+-------+-------------------+
| Input | Valid | ISBN-13 |
+-------------------+-------+-------------------+
| 978-0-8070-8305-4 | no | - |
| 0807083054 | yes | 978-0-8070-8305-5 |
| 978-0-8070-8305-5 | yes | 978-0-8070-8305-5 |
| 0-441-17271-X | no | - |
+-------------------+-------+-------------------+
$ php artisan about --only=isbn
ISBN ..........................................................
Separator ................................................... -
$ php artisan vendor:publish --tag=isbn-config
Copying file [../packages/isbn/config/isbn.php] to [config/isbn.php] ...... DONE
# with 'separator' => ' ' in config/isbn.php:
Blade::render('<x-isbn value="0807083054" />') <span class="isbn" title="ISBN-13">978 0 8070 8305 5</span>
Isbn::format('0807083054') 978 0 8070 8305 5
Source: Laravel — Package Configuration
The first line is a finding about Bookshelf’s own data. All four ISBNs that CatalogueSeeder writes on
Migrations, Seeders and Factories, including Kindred's
978-0-8070-8305-4 used in the examples on earlier pages, fail the checksum, while a known valid number such as
978-0-14-143947-1 passes. The application’s own Isbn13 rule rejects them too. The seeder wrote them directly, so no
validation ran. A
data-quality command like isbn:check, run over the whole table, finds such rows.
Testing with Testbench
Orchestra Testbench boots a minimal Laravel application around the package, so its provider, configuration, views and commands can be tested without a host application:
<?php
// tests/TestCase.php
namespace Bookshelf\Isbn\Tests;
use Bookshelf\Isbn\IsbnServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;
abstract class TestCase extends Orchestra
{
protected function getPackageProviders($app): array
{
return [IsbnServiceProvider::class];
}
protected function defineEnvironment($app): void
{
$app['config']->set('isbn.separator', '-');
}
}
Source: Orchestra Testbench — TestCase
<?php
// tests/IsbnTest.php
namespace Bookshelf\Isbn\Tests;
use Bookshelf\Isbn\Facades\Isbn;
use Illuminate\Support\Facades\Blade;
use PHPUnit\Framework\Attributes\DataProvider;
class IsbnTest extends TestCase
{
public static function isbns(): array
{
return [
'ISBN-10' => ['0807083054', true],
'ISBN-10 with X' => ['080442957X', true],
'ISBN-13' => ['978-0-8070-8305-5', true],
'wrong check digit' => ['978-0-8070-8305-4', false],
'too short' => ['12345', false],
];
}
#[DataProvider('isbns')]
public function test_it_validates_lengths_and_checksums(string $isbn, bool $valid): void
{
$this->assertSame($valid, Isbn::isValid($isbn));
}
public function test_it_formats_with_the_configured_separator(): void
{
$this->assertSame('978-0-8070-8305-5', Isbn::format('0807083054'));
config(['isbn.separator' => ' ']);
$this->app->forgetInstance(\Bookshelf\Isbn\Isbn::class);
Isbn::clearResolvedInstance(\Bookshelf\Isbn\Isbn::class);
$this->assertSame('978 0 8070 8305 5', Isbn::format('0807083054'));
}
public function test_it_renders_the_blade_component(): void
{
$this->assertStringContainsString('978-0-8070-8305-5', Blade::render('<x-isbn value="0807083054" />'));
}
public function test_the_command_reports_invalid_input(): void
{
$this->artisan('isbn:check', ['isbn' => ['978-0-8070-8305-4']])->expectsOutputToContain('no')->assertSuccessful();
}
}
Source: Laravel — Package Development
$ vendor/bin/phpunit
OK (8 tests, 10 assertions)
Source: Orchestra Testbench
A normal composer install in the package directory would give it its own vendor/. Here that install filled the
disk (it clones the whole framework with --prefer-source), so the tests ran with Bookshelf’s vendor/, which had
Testbench 11.5 added, through a small bootstrap file. Pest needs a project root with its own vendor/, so the tests
use PHPUnit classes. In CI, the package is tested against each Laravel version it claims, with a matrix over
illuminate/* versions and --prefer-lowest.
spatie/laravel-package-tools removes most of the provider boilerplate
(->hasConfigFile()->hasViews()->hasCommand(…)), and many published packages use it.
The ecosystem
Versions from Packagist on 2026-10-05, all supporting Laravel 13 unless noted:
| Package | Version | Use |
|---|---|---|
First-party |
||
|
16.8.0, 2.8.1 |
Subscriptions, invoices and webhooks for Stripe or Paddle |
|
1.26.0 |
Feature flags (Processes, Concurrency and Context) |
|
1.32.1 |
Code style on PHP-CS-Fixer, with a Laravel preset |
|
2.12.2 |
Task runner for deploy scripts over SSH, with Blade syntax |
|
1.2.0 |
Page-based routing from files in |
Precognition |
in the framework |
Live validation from front-end forms using the server’s rules (Validation and Form Requests) |
|
11.8.0 |
Full-text search drivers (Redis, MongoDB and Search) |
|
5.31.0 |
OAuth login with GitHub, Google and others (Authentication) |
Nova, Spark |
commercial |
Admin panel, and SaaS billing scaffolding. Licensed, not on Packagist |
Third-party |
||
|
5.9.0 |
Admin panels, forms and tables on Livewire |
|
8.3.0 |
Roles and permissions in the database |
|
11.23.9 |
File attachments for models, with conversions |
|
10.3.3, 5.1.1, 4.23.0 |
Backups, audit logs, typed data objects |
|
3.12.3 |
PHPStan extension that understands Laravel’s magic |
|
6.71.0 |
GraphQL server (Building APIs and API Resources) |
|
4.4.4 |
In-page debug toolbar |
Legacy |
||
Mix, Homestead, Valet |
maintained |
Replaced in the documentation by Vite, Sail or Herd |
Breeze, Jetstream |
2.4.2, 5.5.3 (frozen) |
Replaced by the starter kits (Inertia and Starter Kits) |
Before adding a package: check that it supports the current Laravel version (the illuminate/* constraints),
that releases are recent, how many maintainers it has, and what composer audit says. A package that only saves a
few lines is a dependency for the life of the application.
Where older books and tutorials are outdated
-
Registering every package’s provider and facade in
config/app.php(Stauffer ch. 11) is unnecessary since package discovery in Laravel 5.5.bootstrap/providers.phpholds only the application’s own providers. -
illuminate/supportas the only dependency works for a package that uses only the support classes. Packages that touch Foundation must say so. -
Testing a package inside a host application is replaced by Testbench.
-
Laravel Mix, Homestead and Valet still work. Vite, Sail and Herd are what the documentation describes.