Building Web Apps with Laravel Module 17 – Architecture, multi-tenancy and packages
Building and publishing a Laravel package
Turn validation rules shared by several Laravel apps into one Composer package with a service provider, config and translations, then test and release it.
What you will learn
- Structure a Composer package with a service provider, publishable config, translations and an Artisan command
- Test a package with Orchestra Testbench on Laravel 12 and Laravel 13 in a CI matrix
- Choose illuminate constraints and the semantic version number of each release
- Publish a package on Packagist once composer validate and composer audit have run
- Add AI guidelines and a skill for Laravel Boost to a package
Before you start
What you’ll learn · Before you start
What you will learn
- Structure a Composer package with a service provider, publishable config, translations and an Artisan command
- Test a package with Orchestra Testbench on Laravel 12 and Laravel 13 in a CI matrix
- Choose illuminate constraints and the semantic version number of each release
- Publish a package on Packagist once composer validate and composer audit have run
- Add AI guidelines and a skill for Laravel Boost to a package
Before you start
On this page
Pathshala runs three Laravel apps, for admissions, fees and parents, and each kept its own copy of the checks for PAN, IFSC and PIN code values. A fix to the PIN code check reached one copy and missed the other two. Building a Laravel package turns the three copies into one Composer package that every app requires, and publishing a Laravel package makes each fix one release that every app takes with composer update.
A Laravel package differs from a plain Composer: PHP's dependency manager. It reads composer.json, downloads the packages a project needs from Packagist and installs them into the vendor folder. More in “Install PHP, Composer and Laravel 13” package by one key in its composer.json: extra.laravel names its service provider: A class that sets one part of the application up. Its register method binds services into the container, and its boot method uses them once every provider has registered. More in “How Laravel handles a request”, and Laravel loads that provider when an app installs the package, with its config, messages and Artisan commands, without a line of setup in the app. Here you build pathshala/indian-rules, four validation rules with English and Hindi messages, and install it into a fresh app.
One key in composer.json makes it a Laravel package
Building the package starts with a folder that has its own composer.json. Under extra.laravel, providers lists the service provider and aliases the facade: A class that names one entry of Laravel's container and forwards every static call on it to the object stored there, as Cache::get() forwards to the cache manager. More in “Service providers, facades and contracts”; the rest of the folder is ordinary Composer and PHP:
| Path | What it holds |
|---|---|
composer.json |
name, constraints, extra.laravel |
src/ |
the provider, a service, four rules, a facade, a command |
config/indian-rules.php |
defaults an app may copy and edit |
lang/en, lang/hi |
the messages |
resources/boost/ |
notes for coding assistants |
tests/ |
the package’s own tests |
The provider is the package’s entry point. Its register() merges the default config under the key indian-rules and binds the service the facade reaches. Its boot() calls loadTranslationsFrom() with the namespace indian-rules, so a rule fails with the message key indian-rules::validation.pan; publishes() with a publish tag: A name a package gives to one group of files in publishes(), so php artisan vendor:publish --tag=<name> copies only that group, such as the config or the language files, into the app. for each group of files an app may copy; and commands() for the Artisan command. Read the eight files that matter:
install/packages/indian-rules/composer.json
{
"name": "pathshala/indian-rules",
"description": "Validation rules for Indian identifiers (PAN, GSTIN, IFSC, PIN code) for Laravel, with English and Hindi messages.",
"type": "library",
"license": "MIT",
"keywords": ["laravel", "validation", "india"],
"require": {
"php": "^8.3",
"composer-runtime-api": "^2.2",
"illuminate/console": "^12.0 || ^13.0",
"illuminate/contracts": "^12.0 || ^13.0",
"illuminate/support": "^12.0 || ^13.0",
"illuminate/validation": "^12.0 || ^13.0"
},
"require-dev": {
"orchestra/testbench": "^10.12 || ^11.3"
},
"autoload": {
"psr-4": {
"Pathshala\\IndianRules\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Pathshala\\IndianRules\\Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Pathshala\\IndianRules\\IndianRulesServiceProvider"
],
"aliases": {
"IndianRules": "Pathshala\\IndianRules\\Facades\\IndianRules"
}
}
},
"scripts": {
"test": "phpunit"
},
"config": {
"sort-packages": true
}
} Service provider · install/packages/indian-rules/src/IndianRulesServiceProvider.php
<?php
namespace Pathshala\IndianRules;
use Composer\InstalledVersions;
use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Support\ServiceProvider;
use Pathshala\IndianRules\Console\CheckCommand;
class IndianRulesServiceProvider extends ServiceProvider
{
/**
* Bind the package's services and merge its default config under the app's own copy.
*/
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/indian-rules.php', 'indian-rules');
$this->app->singleton(IndianRules::class);
}
/**
* Tell Laravel where the translations are, what an app may publish, and what the package adds to Artisan.
*/
public function boot(): void
{
$this->loadTranslationsFrom(__DIR__.'/../lang', 'indian-rules');
$this->publishes([
__DIR__.'/../config/indian-rules.php' => config_path('indian-rules.php'),
], 'indian-rules-config');
$this->publishes([
__DIR__.'/../lang' => $this->app->langPath('vendor/indian-rules'),
], 'indian-rules-lang');
if ($this->app->runningInConsole()) {
$this->commands([CheckCommand::class]);
AboutCommand::add('Indian rules', fn () => [
'Version' => InstalledVersions::getPrettyVersion('pathshala/indian-rules'),
'Normalise input' => config('indian-rules.normalise') ? 'on' : 'off',
]);
}
}
} install/packages/indian-rules/src/IndianRules.php
<?php
namespace Pathshala\IndianRules;
use Illuminate\Contracts\Config\Repository;
/**
* The checks the package's rule classes share, and the object the IndianRules facade reaches.
*/
class IndianRules
{
private const BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
public function __construct(private Repository $config)
{
}
/**
* The value as the rules read it: in capitals and without spaces, unless the app turned that off.
*/
public function normalise(string $value): string
{
if (! $this->config->get('indian-rules.normalise')) {
return $value;
}
return strtoupper(str_replace(' ', '', $value));
}
/**
* Ten characters, in the shape this package accepts: five letters, four digits and a letter.
*/
public function isPan(string $value): bool
{
return preg_match('/^[A-Z]{5}[0-9]{4}[A-Z]$/', $this->normalise($value)) === 1;
}
/**
* Eleven characters: four letters for the bank, a zero, then six letters or digits for the branch.
*/
public function isIfsc(string $value): bool
{
return preg_match('/^[A-Z]{4}0[A-Z0-9]{6}$/', $this->normalise($value)) === 1;
}
/**
* Six digits; "411 001" is read as 411001 while normalising is on.
*/
public function isPincode(string $value): bool
{
return preg_match('/^[0-9]{6}$/', $this->normalise($value)) === 1;
}
/**
* What is wrong with a 15-character tax number: 'shape', 'state', 'check', or null when nothing is.
* The shape: a two-digit state code, a PAN, a two-character entity code and one check character.
*/
public function gstinProblem(string $value): ?string
{
$value = $this->normalise($value);
if (preg_match('/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][0-9A-Z]{2}[0-9A-Z]$/', $value) !== 1) {
return 'shape';
}
$states = $this->config->get('indian-rules.gstin.states');
if (is_array($states) && ! in_array(substr($value, 0, 2), $states, true)) {
return 'state';
}
if ($this->config->get('indian-rules.gstin.check_character') && $value[14] !== $this->checkCharacter(substr($value, 0, 14))) {
return 'check';
}
return null;
}
/**
* The check character of the first fourteen characters, computed as Luhn mod 36 over their base-36 values.
* The rules that define the number say there is one check character, not how it is computed: compare this
* with the issuer's published method before a live form refuses numbers with it.
*/
public function checkCharacter(string $first14): string
{
$sum = 0;
$factor = 2;
for ($i = 13; $i >= 0; $i--) {
$addend = $factor * strpos(self::BASE36, $first14[$i]);
$factor = $factor === 2 ? 1 : 2;
$sum += intdiv($addend, 36) + $addend % 36;
}
return self::BASE36[(36 - $sum % 36) % 36];
}
} Rules/Pan.php · install/packages/indian-rules/src/Rules/Pan.php
<?php
namespace Pathshala\IndianRules\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Pathshala\IndianRules\IndianRules;
class Pan implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (! is_string($value) || ! app(IndianRules::class)->isPan($value)) {
$fail('indian-rules::validation.pan')->translate();
}
}
} Console/CheckCommand.php · install/packages/indian-rules/src/Console/CheckCommand.php
<?php
namespace Pathshala\IndianRules\Console;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Pathshala\IndianRules\Rules\Gstin;
use Pathshala\IndianRules\Rules\Ifsc;
use Pathshala\IndianRules\Rules\Pan;
use Pathshala\IndianRules\Rules\Pincode;
class CheckCommand extends Command
{
protected $signature = 'indian-rules:check
{rule : pan, gstin, ifsc or pincode}
{value : the value to check}
{--locale= : the language of the messages, such as hi}';
protected $description = 'Check one value with one of the indian-rules rules';
private const RULES = ['pan' => Pan::class, 'gstin' => Gstin::class, 'ifsc' => Ifsc::class, 'pincode' => Pincode::class];
public function handle(): int
{
$rule = self::RULES[$this->argument('rule')] ?? null;
if ($rule === null) {
$this->error('Unknown rule: use pan, gstin, ifsc or pincode.');
return self::INVALID;
}
if ($this->option('locale')) {
$this->laravel->setLocale($this->option('locale'));
}
$validator = Validator::make(['value' => $this->argument('value')], ['value' => [new $rule()]]);
if ($validator->passes()) {
$this->line('valid');
return self::SUCCESS;
}
foreach ($validator->errors()->all() as $message) {
$this->line($message);
}
return self::FAILURE;
}
} config · install/packages/indian-rules/config/indian-rules.php
<?php
return [
/*
* Upper-case the value and drop spaces before checking, so a number typed as "abcpe 1234 f" is checked as
* ABCPE1234F. Set it to false to check exactly what was sent.
*/
'normalise' => true,
/*
* The fifteen-character tax number: which state codes the app accepts (null for every code) and whether the
* last character is checked against the first fourteen.
*/
'gstin' => [
'states' => null,
'check_character' => true,
],
]; lang/en · install/packages/indian-rules/lang/en/validation.php
<?php
return [
'pan' => 'The :attribute field must be a 10-character PAN, such as ABCPE1234F.',
'ifsc' => 'The :attribute field must be an 11-character IFSC, such as ABCD0123456.',
'pincode' => 'The :attribute field must be a PIN code of six digits.',
'gstin' => [
'shape' => 'The :attribute field must be a 15-character number, such as 29ABCDE1234F1ZW.',
'state' => 'The :attribute field starts with a state code this app does not accept.',
'check' => 'The last character of :attribute does not match the other fourteen.',
],
]; lang/hi · install/packages/indian-rules/lang/hi/validation.php
<?php
return [
'pan' => ':attribute दस वर्णों का PAN होना चाहिए, जैसे ABCPE1234F।',
'ifsc' => ':attribute ग्यारह वर्णों का IFSC होना चाहिए, जैसे ABCD0123456।',
'pincode' => ':attribute छह अंकों का पिन कोड होना चाहिए।',
'gstin' => [
'shape' => ':attribute 29ABCDE1234F1ZW जैसी 15 वर्णों की संख्या होनी चाहिए।',
'state' => ':attribute ऐसे राज्य कोड से शुरू होता है जिसे यह ऐप स्वीकार नहीं करता।',
'check' => ':attribute का अंतिम वर्ण बाकी चौदह वर्णों से मेल नहीं खाता।',
],
]; The constraints say which versions of PHP and of the framework’s illuminate/* components the package works with: ^12.0 || ^13.0 allows any 12.x or 13.x release, and the double pipe is the logical OR that Composer’s documentation recommends.
The identifiers the rules check
Rule 10 of the CGST Rules builds the 15-character GSTIN from a two-character state code, a ten-character PAN or TAN, a two-character entity code and one checksum character; the package accepts numbers built on a PAN only. The RBI describes a branch’s IFSC as 11 characters with 0 in fifth place, and the package checks a PIN code as six digits.
A fresh app installs the package without one line of setup
While the package has no release, an app can require it from a folder beside it through a path repository: A Composer repository of type path that points at a folder on the same disk, so an app can require a package that has no release yet; Composer links or copies that folder into vendor/.. Composer then links the folder into vendor/, and Laravel’s package discovery: Laravel's step after every Composer autoload dump that reads each installed package's extra.laravel section and records its service providers and facade aliases in bootstrap/cache/packages.php, so an app needs no setup to load them. picks up the provider from extra.laravel. Step through what happens between composer require and the first check:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- The package is required. composer require found pathshala/indian-rules in a path repository, a folder beside the app, and linked it into vendor/ as version 1.0.0. The app's composer.json now asks for ^1.0.
- Autoloading is rebuilt. Composer regenerated its autoloader, so the namespace Pathshala\IndianRules\ now loads from the package's src/ folder, then it ran the script that Laravel's skeleton lists after every autoload dump.
- Discovery reads extra.laravel. That script is php artisan package:discover. It reads the extra.laravel section of every installed package; the recorded run lists seven packages, the last one pathshala/indian-rules.
- The manifest is written. bootstrap/cache/packages.php now holds the provider class and the IndianRules alias. The app's own bootstrap/providers.php was not edited.
- register() runs first. On the next command, Laravel reads the manifest and calls register() on the provider: it merges the default config under the key indian-rules and binds the IndianRules service once for the whole app.
- boot() runs after register(). Once all providers are registered, boot() names the folder of the English and Hindi messages, the files an app may publish and the Artisan command the package adds.
- The app takes its own copy. php artisan vendor:publish --tag=indian-rules-config copied the defaults to config/indian-rules.php. The app edits that copy; the file in the package stays the default.
- The checks work. The command said valid for abcpe 1234 f, refused a tax number with a wrong last character in English and in Hindi, and the facade worked out W, with no setup written in the app.
The same install, recorded in a new Pathshala app. Two checks are meant to fail, so the page shows the message a wrong check character gets:
from session import run
# A fresh Pathshala app installs pathshala/indian-rules from a folder beside it (a path repository), as a team does
# while the package has no release yet. Package discovery writes the provider and the alias into
# bootstrap/cache/packages.php; vendor:publish copies the config; the command, the Hindi messages and the facade
# then work without one line of setup in the app. Two checks are meant to fail (lines with "!"): they show the
# message a wrong check character gets.
run(
"""
export COMPOSER_FUND=0
composer config repositories.indian-rules '{"type": "path", "url": "packages/indian-rules", "options": {"versions": {"pathshala/indian-rules": "1.0.0"}}}'
composer require pathshala/indian-rules
grep -A11 "'pathshala/indian-rules'" bootstrap/cache/packages.php
php artisan vendor:publish --tag=indian-rules-config
php artisan indian-rules:check pan "abcpe 1234 f"
! php artisan indian-rules:check gstin 29ABCDE1234F1Z0
! php artisan indian-rules:check gstin 29ABCDE1234F1Z0 --locale=hi
php artisan tinker --execute 'echo IndianRules::checkCharacter("29ABCDE1234F1Z"), PHP_EOL;'
php artisan about --only=indian_rules
""",
app=True,
overlay=("install",),
) Output
$ export COMPOSER_FUND=0
$ composer config repositories.indian-rules '{"type": "path", "url": "packages/indian-rules", "options": {"versions": {"pathshala/indian-rules": "1.0.0"}}}'
$ composer require pathshala/indian-rules
./composer.json has been updated
Running composer update pathshala/indian-rules
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking pathshala/indian-rules (1.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing pathshala/indian-rules (1.0.0): Symlinking from packages/indian-rules
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
INFO Discovering packages.
laravel/pail .......................................................... DONE
laravel/pao ........................................................... DONE
laravel/tinker ........................................................ DONE
nesbot/carbon ......................................................... DONE
nunomaduro/collision .................................................. DONE
nunomaduro/termwind ................................................... DONE
pathshala/indian-rules ................................................ DONE
> @php artisan vendor:publish --tag=laravel-assets --ansi --force
INFO No publishable resources for tag [laravel-assets].
No security vulnerability advisories found.
Using version ^1.0 for pathshala/indian-rules
$ grep -A11 "'pathshala/indian-rules'" bootstrap/cache/packages.php
'pathshala/indian-rules' =>
array (
'providers' =>
array (
0 => 'Pathshala\\IndianRules\\IndianRulesServiceProvider',
),
'aliases' =>
array (
'IndianRules' => 'Pathshala\\IndianRules\\Facades\\IndianRules',
),
),
);
$ php artisan vendor:publish --tag=indian-rules-config
INFO Publishing [indian-rules-config] assets.
Copying file [packages/indian-rules/config/indian-rules.php] to [config/indian-rules.php] DONE
$ php artisan indian-rules:check pan "abcpe 1234 f"
valid
$ php artisan indian-rules:check gstin 29ABCDE1234F1Z0
The last character of value does not match the other fourteen.
[exit status 1]
$ php artisan indian-rules:check gstin 29ABCDE1234F1Z0 --locale=hi
value का अंतिम वर्ण बाकी चौदह वर्णों से मेल नहीं खाता।
[exit status 1]
$ php artisan tinker --execute 'echo IndianRules::checkCharacter("29ABCDE1234F1Z"), PHP_EOL;'
W
$ php artisan about --only=indian_rules
Indian rules ...............................................................
Normalise input ......................................................... on
Version .............................................................. 1.0.0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 install_session.py
Besides composer.lock, the app’s own files changed in two places only: composer.json gained the repository and ^1.0, and vendor:publish added config/indian-rules.php. Discovery listed the package beside six others and wrote its provider and alias into bootstrap/cache/packages.php. The command accepted abcpe 1234 f because the default config upper-cases the value and drops spaces before checking. The Hindi message came from the package’s lang/hi file, and about showed the version that Composer installed.
Key idea
The app requires the package and copies only the files it wants to change; the provider named in extra.laravel does every other step, which is what makes a Composer package a Laravel package.
Checkpoint
Check: What makes a Composer package a Laravel package · Installing a package and package discovery
Four more things decide whether other teams can depend on the package. Orchestra Testbench runs the package’s tests inside a small Laravel application it builds for each test. A CI matrix: A CI job definition that runs once for every combination of the values it lists, for example three PHP versions times two Laravel versions: six jobs. runs those tests once for each PHP and Laravel version the constraints allow. semantic versioning: A way of numbering releases as major.minor.patch: a new major version may break code written for the one before, a new minor adds features without breaking anything, and a patch only fixes bugs. More in “What Laravel is and when to use it” tells an app which releases can break it, and publishing a Laravel package on Packagist: The default package repository for Composer. Composer asks it where to download a package such as laravel/framework. More in “Install PHP, Composer and Laravel 13” turns each git tag into a version. Laravel Boost reads guidelines and skills that a package ships, so coding assistants use the package as intended.
Testing the package with Testbench on Laravel 12 and 13 Pro: opens with a Pro, Premium or Ultimate pass
A package has no application of its own to run its tests in, yet it promises to work in every app on every Laravel version its constraints allow. Two recorded runs show what that takes: a Testbench suite that catches a config bug only an app with a published config would meet, and a two-leg matrix that catches a change only Laravel 12 refuses.
In this part:
- A test case that boots the package the way an app would
- Why a published config with one key lost the other
- A matrix of PHP and Laravel versions, and the failure only one leg catches
4 runnable examples (.github/workflows/tests.yml, CheckCommand.php with attributes, CommandTest.php, ConfigTest.php, MessagesTest.php, Python, RulesTest.php, phpunit.xml.dist, tests/TestCase.php) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Versions, release checks, Packagist and Boost Pro: opens with a Pro, Premium or Ultimate pass
A release is a promise to every app that requires the package, so its number and its constraints have to say exactly what changed and what was tested. The runs below check a release before its tag, and the rules turn each kind of change into a version number.
In this part:
- Constraints that claim only what the matrix proves
- Which change needs a major, a minor or a patch number
- The checks before a tag: validate, the root version and audit
- Publishing on Packagist, one tag per version
- Notes for coding assistants: Boost guidelines and skills
- A rubric for a package another team can depend on
3 runnable examples (Python, guidelines/core.blade.php, skills/indian-rules-validation/SKILL.md) · about 9 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practise
In your own Pathshala app, make a folder packages/indian-rules with the eight files above, add it as a path repository and require it. Then add a fifth rule, BatchCode, for Pathshala’s own batch codes, three capital letters, a hyphen and four digits such as MAT-0412, with an English and a Hindi message, and check it with php artisan indian-rules:check before and after you publish the language files.
Practice that adapts to you
Practise this lesson
7 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): which file does Laravel’s package discovery write, and what does it hold?
Package discovery writes bootstrap/cache/packages.php, a list of each installed package’s service providers and facade aliases. The Laravel skeleton runs php artisan package:discover after every Composer autoload dump; it reads the extra.laravel section of every package in vendor/composer/installed.json and writes that file. Laravel registers those providers on every request and command, so the app’s own bootstrap/providers.php is never edited. An app that wants to register a package’s provider itself lists the package under extra.laravel.dont-discover in its own composer.json.
Three longer questions build on this lesson:
-
Mid to senior
Three Laravel apps of one company each hold a copy of the same validation rules and a trait that applies them. A teammate proposes a private Composer package instead. When is the package the right move rather than the copied trait, and what does it cost the team?
-
Mid to staff
You maintain a Laravel package that supports Laravel 12 and 13 on PHP 8.3 to 8.5. Design its CI: what runs on every pull request, how the jobs are built, and how you read a failure that appears on one job only.
-
Mid to senior
Your Laravel package is at 2.3.1. Name the kinds of change that force 3.0.0, beyond renaming or removing a PHP class, and say how you would ship one of them without stranding the apps that depend on you.
Flashcards · 5 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What makes a Composer package a Laravel package?
One key in its composer.json: extra.laravel, whose providers list names the service provider (and aliases the facade). Laravel's package discovery loads that provider when an app installs the package, with no setup in the app.
-
What does the package's service provider do in register() and boot()?
register() merges the default config under the package's key and binds the service the facade reaches. boot() loads the translations under a namespace, so messages read as indian-rules::validation.pan, registers publishes() tags for files an app may copy, and adds the Artisan command.
-
What does a constraint of ^12.0 || ^13.0 on illuminate components say?
That the package works with any 12.x or any 13.x release of the framework's components; the double pipe is the logical OR that Composer's documentation recommends.
-
How can an app install a package that has no release yet?
Through a path repository pointing at the package's folder: Composer links the folder into vendor/, and package discovery writes its provider and alias into bootstrap/cache/packages.php. vendor:publish then copies only the files the app wants to change, such as its config.
-
Which four things decide whether other teams can depend on a package?
Orchestra Testbench, which runs its tests inside a small Laravel app built for each test; a CI matrix over every PHP and Laravel version the constraints allow; semantic versioning, so apps know which releases can break them; and publishing on Packagist, where each git tag becomes a version.
Key takeaways
- A Laravel package is a Composer package whose
composer.jsonnames its service provider and facade underextra.laravel. - Package discovery runs after every autoload dump and writes the provider and the alias into
bootstrap/cache/packages.php, so the app needs no setup. - The provider merges its default config and binds its services in
register(), and loads messages, declares publishable files and adds commands inboot(). - An app copies only the files it wants to change, with
vendor:publishand the package’s tag, and edits that copy.
Terms in this lesson
- CI matrix
- A CI job definition that runs once for every combination of the values it lists, for example three PHP versions times two Laravel versions: six jobs.
- Orchestra Testbench
- A development dependency of Laravel packages that builds a small Laravel application for each test, so a package's tests run without a host app.
- package discovery
- Laravel's step after every Composer autoload dump that reads each installed package's extra.laravel section and records its service providers and facade aliases in bootstrap/cache/packages.php, so an app needs no setup to load them.
- path repository
- A Composer repository of type path that points at a folder on the same disk, so an app can require a package that has no release yet; Composer links or copies that folder into vendor/.
- publish tag
- A name a package gives to one group of files in publishes(), so php artisan vendor:publish --tag=<name> copies only that group, such as the config or the language files, into the app.
References
- Laravel 13.x package development (Laravel (laravel.com))
- Laravel 13.x Artisan console, command signatures (Laravel (laravel.com))
- Laravel 13.x release notes (Laravel (laravel.com))
- Laravel 13.x Laravel Boost, third-party package guidelines and skills (Laravel (laravel.com))
- laravel/framework 13.x source: Illuminate/Support/ServiceProvider.php (Laravel (GitHub))
- laravel/framework 13.x source: Illuminate/Foundation/PackageManifest.php (Laravel (GitHub))
- laravel/framework 13.x source: Illuminate/Console/Command.php (Laravel (GitHub))
- PHP manual: reading attributes with the Reflection API (The PHP Group (php.net))
- laravel/framework 11.x changelog, the option to replace configs recursively (Laravel (GitHub))
- laravel/boost v2.10.1 source: Install/ThirdPartyPackage.php (Laravel (GitHub))
- orchestra/testbench on Packagist, versions and requirements (Packagist)
- Orchestra Testbench (Packages Toolkit for Laravel (Orchestra Platform))
- Composer: the composer.json schema (Composer (getcomposer.org))
- Composer: versions and constraints (Composer (getcomposer.org))
- Composer: command-line interface, validate and audit (Composer (getcomposer.org))
- Composer: repositories, the path type (Composer (getcomposer.org))
- Composer: libraries, versioning from tags (Composer (getcomposer.org))
- Composer: troubleshooting, root package version detection (Composer (getcomposer.org))
- Semantic Versioning 2.0.0 (semver.org)
- About Packagist (Packagist)
- GitHub Actions: running variations of jobs in a workflow (GitHub Docs)
- CGST Rules 2017, rule 10, issue of registration certificate (Central Board of Indirect Taxes and Customs)
- NEFT frequently asked questions, the IFSC (Reserve Bank of India)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress