Loading...
Loading...
Guide for creating PHP and Laravel packages using Spatie's package-skeleton-laravel and package-skeleton-php templates. Use when the user wants to create a new PHP or Laravel package, scaffold a package. Also use when building customizable packages — covers proven patterns for extensibility (events, configurable models/jobs, action classes) instead of config option creep.
npx skill4agent add freekmurze/dotfiles spatie-package-skeletonghphpcomposerspatielaravel-cool-featuregit config user.namegit config user.emailgh auth statusSpatielaravel-CoolFeaturegh repo create <vendor>/<package-name> --template spatie/package-skeleton-laravel --public --clone
cd <package-name>--private--publicconfigure.phpgh auth statusgit loggit configsedfind . -type f -not -path './.git/*' -not -path './vendor/*' -not -name 'configure.php' -exec sed -i '' \
-e 's/:author_name/Author Name/g' \
-e 's/:author_username/authorusername/g' \
-e 's/author@domain\.com/author@email.com/g' \
-e 's/:vendor_name/Vendor Name/g' \
-e 's/:vendor_slug/vendorslug/g' \
-e 's/VendorName/VendorNamespace/g' \
-e 's/:package_slug_without_prefix/package-without-prefix/g' \
-e 's/:package_slug/package-name/g' \
-e 's/:package_name/package-name/g' \
-e 's/:package_description/Package description here/g' \
-e 's/Skeleton/ClassName/g' \
-e 's/skeleton/package-name/g' \
-e 's/migration_table_name/package_without_prefix/g' \
-e 's/variable/variableName/g' \
{} +-e:package_slug_without_prefix:package_slugSkeletonskeletonmv src/Skeleton.php src/ClassName.php
mv src/SkeletonServiceProvider.php src/ClassNameServiceProvider.php
mv src/Facades/Skeleton.php src/Facades/ClassName.php
mv src/Commands/SkeletonCommand.php src/Commands/ClassNameCommand.php
mv config/skeleton.php config/package-without-prefix.php
mv database/migrations/create_skeleton_table.php.stub database/migrations/create_package_without_prefix_table.php.stubconfigure.phpcomposer installrm configure.php
composer installcomposer install# Check the directory structure
ls -la src/
# Verify composer.json looks correct
cat composer.json | head -20
# Check tests passed during setupgit add -A
git commit -m "Configure package skeleton"
git push -u origin mainhttps://github.com/<vendor>/<package-name>VendorNamespace\ClassNamesrc/<ClassName>.phpsrc/<ClassName>ServiceProvider.phpconfig/<package-slug>.phptests/src/
YourClass.php # Main package class
YourClassServiceProvider.php # Service provider (uses spatie/laravel-package-tools)
Facades/YourClass.php # Facade
Commands/YourClassCommand.php # Artisan command stub
config/
your-package.php # Published config file
database/
factories/ModelFactory.php # Factory template (commented out)
migrations/create_table.php.stub # Migration stub
resources/views/ # Blade views
tests/
TestCase.php # Extends Orchestra\Testbench\TestCase
ArchTest.php # Architecture tests (no dd/dump/ray)
ExampleTest.php # Starter test
Pest.php # Pest config binding TestCasespatie/laravel-package-toolspublic function configurePackage(Package $package): void
{
$package
->name('your-package')
->hasConfigFile()
->hasViews()
->hasMigration('create_your_package_table')
->hasCommand(YourClassCommand::class);
}database/->hasMigration()src/Commands/->hasCommand()resources/views/->hasViews()src/Facades/composer.jsonextra.laravel.aliasesconfig/->hasConfigFile()composer test # Run tests
composer format # Run code style fixer
composer analyse # Run static analysisuse Spatie\LaravelPackageTools\Commands\InstallCommand;
$package->hasInstallCommand(function (InstallCommand $command) {
$command
->publishConfigFile()
->publishMigrations()
->askToRunMigrations()
->askToStarRepoOnGitHub('vendor/package-name');
});clear()forget()save()php-guidelines-from-spatie$thisPdf::view('invoice', $data)->format('a4')->landscape()->save('invoice.pdf');// Factory intercepts calls via __call() to create fresh builder instances
class PdfFactory {
public function __call($method, $parameters) {
return (clone $this->builder)->$method(...$parameters);
}
}PdfOptionsScreenshotOptionsclass CouldNotGeneratePdf extends Exception
{
public static function browsershotNotInstalled(): static
{
return new static('To use Browsershot, install it via `composer require spatie/browsershot`.');
}
}Conditionablewhen()unless()MacroableDumpableinterface PdfDriver {
public function generatePdf(string $html, ...): string;
public function savePdf(string $html, ..., string $path): void;
}// config/your-package.php
'driver' => env('LARAVEL_PDF_DRIVER', 'browsershot'),
'cache_profile' => App\CacheProfiles\CustomCacheProfile::class,
'hasher' => App\Hashers\CustomHasher::class,::fake()Pdf::fake();
// ... code that generates PDFs ...
Pdf::assertSaved(fn ($pdf, $path) => $path === 'invoice.pdf');
Pdf::assertQueued();
Pdf::assertNotQueued();event(new TransformerStarting($transformer, $url));
$transformer->transform();
event(new TransformerEnded($transformer, $url, $result));// config
'model' => Spatie\Package\Models\Result::class,
// In package code — always resolve from config:
$model = config('your-package.model');
$model::find($id);'process_job' => Spatie\Package\Jobs\ProcessJob::class,'actions' => [
'fetch_content' => Spatie\Package\Actions\FetchContentAction::class,
],saveQueued()PendingDispatchthen()catch()Pdf::view('invoice', $data)
->saveQueued('invoice.pdf')
->then(fn ($path) => /* success */)
->catch(fn ($e) => /* failure */)
->onQueue('pdfs');EventNotificationConfig{Service}DriverCould Not...Fakereturn [
/*
* When disabled, the middleware will not convert any responses.
*/
'enabled' => env('PACKAGE_ENABLED', true),
/*
* The driver used to perform the operation.
* Supported: "local", "cloud"
*/
'driver' => env('PACKAGE_DRIVER', 'local'),
'cache' => [
/*
* How long results should be cached, in seconds.
*/
'ttl' => (int) env('PACKAGE_CACHE_TTL', 3600),
],
];/* *///downprotectedprivate