spatie-package-skeleton
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCreating a Laravel Package with Spatie's Skeleton
使用Spatie骨架创建Laravel包
Prerequisites
前提条件
- CLI installed and authenticated
gh - available in PATH
php - available in PATH
composer
- 已安装并认证CLI
gh - 系统PATH中已配置
php - 系统PATH中已配置
composer
Workflow
操作流程
1. Gather Package Details
1. 收集包信息
Ask the user for:
- Vendor name (e.g. ) — the GitHub org or username
spatie - Package name (e.g. ) — the repo/package name
laravel-cool-feature - Package description — one-liner for composer.json
- Visibility — public or private (default: public)
Use defaults where sensible:
- Author name: from
git config user.name - Author email: from
git config user.email - Author username: from
gh auth status - Vendor namespace: PascalCase of vendor name (e.g. )
Spatie - Class name: TitleCase of package name without prefix (e.g.
laravel-)CoolFeature
向用户询问以下信息:
- 供应商名称(例如)——GitHub组织名或用户名
spatie - 包名称(例如)——仓库/包名称
laravel-cool-feature - 包描述——用于composer.json的单行描述
- 可见性——公开或私有(默认:公开)
合理使用默认值:
- 作者名称:取自
git config user.name - 作者邮箱:取自
git config user.email - 作者用户名:取自
gh auth status - 供应商命名空间:供应商名称的大驼峰形式(例如)
Spatie - 类名:去掉前缀后的包名称的标题格式(例如
laravel-)CoolFeature
2. Create the Repository from Template
2. 从模板创建仓库
bash
gh repo create <vendor>/<package-name> --template spatie/package-skeleton-laravel --public --clone
cd <package-name>If the user wants a private repo, use instead of .
--private--publicbash
gh repo create <vendor>/<package-name> --template spatie/package-skeleton-laravel --public --clone
cd <package-name>如果用户需要私有仓库,将替换为。
--public--private3. Configure the Package (Manual Replacement)
3. 配置包(手动替换)
WARNING: Do NOT pipe stdin to . The script's child processes (, , ) consume lines from the piped stdin, causing inputs to shift and produce garbled results. Instead, do the replacements manually:
configure.phpgh auth statusgit loggit config- Run to replace all placeholder strings across the repo:
sed
bash
find . -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' \
{} +Important: The order of flags matters. Replace before to avoid partial matches. Replace (PascalCase) before (lowercase).
-e:package_slug_without_prefix:package_slugSkeletonskeleton- Rename the skeleton files:
bash
mv 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.stub- Delete and run
configure.php:composer install
bash
rm configure.php
composer installUse a longer timeout (5 minutes) for .
composer install警告:不要将标准输入管道到。该脚本的子进程(、、)会消耗管道输入的内容,导致输入错位并产生混乱结果。请手动执行以下替换操作:
configure.phpgh auth statusgit loggit config- 运行命令替换仓库中的所有占位符字符串:
sed
bash
find . -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_slugSkeletonskeleton- 重命名骨架文件:
bash
mv 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.stub- 删除并运行
configure.php:composer install
bash
rm configure.php
composer install为设置更长的超时时间(5分钟)。
composer install4. Verify Setup
4. 验证设置
After the script completes:
bash
undefined脚本执行完成后:
bash
undefinedCheck the directory structure
检查目录结构
ls -la src/
ls -la src/
Verify composer.json looks correct
验证composer.json是否正确
cat composer.json | head -20
cat composer.json | head -20
Check tests passed during setup
检查设置过程中测试是否通过
undefinedundefined5. Initial Commit and Push
5. 初始提交并推送
The configure script modifies all files but doesn't commit. Create the initial commit:
bash
git add -A
git commit -m "Configure package skeleton"
git push -u origin main配置脚本会修改所有文件但不会提交。创建初始提交:
bash
git add -A
git commit -m "Configure package skeleton"
git push -u origin main6. Report to User
6. 向用户反馈
Tell the user:
- The repo URL (e.g. )
https://github.com/<vendor>/<package-name> - The namespace (e.g. )
VendorNamespace\ClassName - Key files to start editing:
- — main package class
src/<ClassName>.php - — service provider
src/<ClassName>ServiceProvider.php - — configuration
config/<package-slug>.php - — test directory
tests/
告知用户以下信息:
- 仓库URL(例如)
https://github.com/<vendor>/<package-name> - 命名空间(例如)
VendorNamespace\ClassName - 需要开始编辑的关键文件:
- —— 包的主类
src/<ClassName>.php - —— 服务提供者
src/<ClassName>ServiceProvider.php - —— 配置文件
config/<package-slug>.php - —— 测试目录
tests/
Post-Setup Reference
搭建后参考
Directory Structure
目录结构
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 TestCasesrc/
YourClass.php # 主包类
YourClassServiceProvider.php # 服务提供者(使用spatie/laravel-package-tools)
Facades/YourClass.php # 门面
Commands/YourClassCommand.php # Artisan命令存根
config/
your-package.php # 可发布的配置文件
database/
factories/ModelFactory.php # 工厂模板(已注释)
migrations/create_table.php.stub # 迁移存根
resources/views/ # Blade视图
tests/
TestCase.php # 继承Orchestra\Testbench\TestCase
ArchTest.php # 架构测试(禁止使用dd/dump/ray)
ExampleTest.php # 入门测试
Pest.php # Pest配置绑定TestCaseService Provider Configuration
服务提供者配置
Uses :
spatie/laravel-package-toolsphp
public function configurePackage(Package $package): void
{
$package
->name('your-package')
->hasConfigFile()
->hasViews()
->hasMigration('create_your_package_table')
->hasCommand(YourClassCommand::class);
}Remove methods you don't need. Delete corresponding directories/files too:
- No database? Delete and remove
database/->hasMigration() - No commands? Delete and remove
src/Commands/->hasCommand() - No views? Delete and remove
resources/views/->hasViews() - No facade? Delete and remove facade alias from
src/Facades/composer.jsonextra.laravel.aliases - No config? Delete and remove
config/->hasConfigFile()
使用:
spatie/laravel-package-toolsphp
public 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.json下的门面别名extra.laravel.aliases - 不需要配置?删除并移除
config/->hasConfigFile()
Testing
测试
bash
composer test # Run tests
composer format # Run code style fixer
composer analyse # Run static analysisbash
composer test # 运行测试
composer format # 运行代码风格修复工具
composer analyse # 运行静态分析Adding an Install Command
添加安装命令
php
use Spatie\LaravelPackageTools\Commands\InstallCommand;
$package->hasInstallCommand(function (InstallCommand $command) {
$command
->publishConfigFile()
->publishMigrations()
->askToRunMigrations()
->askToStarRepoOnGitHub('vendor/package-name');
});php
use Spatie\LaravelPackageTools\Commands\InstallCommand;
$package->hasInstallCommand(function (InstallCommand $command) {
$command
->publishConfigFile()
->publishMigrations()
->askToRunMigrations()
->askToStarRepoOnGitHub('vendor/package-name');
});API Design Principles
API设计原则
- Optimize for easy usage. The API exposed to users should be as simple as possible. Every public method, facade call, and middleware should feel obvious and require minimal setup.
- Use well-named methods. Method names should be intuitive and self-documenting. Prefer descriptive names over terse ones — the user should understand what a method does without reading its implementation. Use verb-first method names (,
clear(),forget()).save() - Follow Spatie PHP/Laravel guidelines. All code must follow the conventions described in the skill.
php-guidelines-from-spatie
- 优化易用性:向用户暴露的API应尽可能简洁。每个公共方法、门面调用和中间件都应直观易懂,且只需最少的配置。
- 使用命名清晰的方法:方法名应直观且自文档化。优先使用描述性名称而非简洁名称——用户无需阅读实现代码就能理解方法的作用。使用动词开头的方法名(、
clear()、forget())。save() - 遵循Spatie PHP/Laravel指南:所有代码必须遵循技能中描述的规范。
php-guidelines-from-spatie
Package patters
包设计模式
Fluent/Chainable APIs
流畅/链式API
Builder-style classes where every setter returns . Users should be able to chain configuration calls naturally.
$thisphp
Pdf::view('invoice', $data)->format('a4')->landscape()->save('invoice.pdf');采用构建器风格的类,每个设置方法都返回。用户应能自然地链式调用配置方法。
$thisphp
Pdf::view('invoice', $data)->format('a4')->landscape()->save('invoice.pdf');Sensible Defaults
合理的默认值
The package should work well out of the box with zero configuration. Only require explicit setup for non-standard use cases. Provide safe defaults in the config file and apply them when values aren't explicitly set.
包应在零配置的情况下就能正常工作。仅在非标准使用场景下才要求显式配置。在配置文件中提供安全的默认值,并在未显式设置值时应用这些默认值。
Facade + Factory for Clean State
门面+工厂保证干净状态
Back facades with a factory that creates a fresh builder per call to prevent state bleed between requests.
php
// Factory intercepts calls via __call() to create fresh builder instances
class PdfFactory {
public function __call($method, $parameters) {
return (clone $this->builder)->$method(...$parameters);
}
}为门面配备工厂,每次调用时创建新的构建器实例,避免请求之间的状态泄漏。
php
// 工厂通过__call()拦截调用,创建新的构建器实例
class PdfFactory {
public function __call($method, $parameters) {
return (clone $this->builder)->$method(...$parameters);
}
}Enums Over Strings
使用枚举替代字符串
Use PHP enums for any fixed set of options instead of string constants. This gives type safety and IDE support.
对于任何固定的选项集合,使用PHP枚举而非字符串常量。这能提供类型安全和IDE支持。
Value Objects for Options
使用值对象管理选项
Group related settings into small readonly classes (like , ) rather than passing many loose parameters between layers.
PdfOptionsScreenshotOptions将相关设置分组到小型只读类中(如、),而非在各层之间传递多个零散参数。
PdfOptionsScreenshotOptionsDescriptive Exception Classes
描述性异常类
Name exceptions after what went wrong and provide static factory methods for specific scenarios with helpful error messages:
php
class CouldNotGeneratePdf extends Exception
{
public static function browsershotNotInstalled(): static
{
return new static('To use Browsershot, install it via `composer require spatie/browsershot`.');
}
}根据错误类型命名异常,并为特定场景提供静态工厂方法及有用的错误信息:
php
class CouldNotGeneratePdf extends Exception
{
public static function browsershotNotInstalled(): static
{
return new static('To use Browsershot, install it via `composer require spatie/browsershot`.');
}
}Traits for Cross-Cutting Concerns
使用Trait处理横切关注点
Use (for / chaining), (for runtime extension), and (for debugging) on builder classes.
Conditionablewhen()unless()MacroableDumpable在构建器类上使用(用于/链式调用)、(用于运行时扩展)和(用于调试)。
Conditionablewhen()unless()MacroableDumpableSmall Interfaces for Extensibility
小型接口提升扩展性
Define interfaces for components users might want to swap. Keep them small — one or two methods is ideal:
php
interface PdfDriver {
public function generatePdf(string $html, ...): string;
public function savePdf(string $html, ..., string $path): void;
}为用户可能想要替换的组件定义接口。保持接口精简——理想情况下只有一到两个方法:
php
interface PdfDriver {
public function generatePdf(string $html, ...): string;
public function savePdf(string $html, ..., string $path): void;
}Config-Driven Class Bindings
基于配置的类绑定
Let users swap implementations via config rather than requiring service provider overrides:
php
// config/your-package.php
'driver' => env('LARAVEL_PDF_DRIVER', 'browsershot'),
'cache_profile' => App\CacheProfiles\CustomCacheProfile::class,
'hasher' => App\Hashers\CustomHasher::class,允许用户通过配置替换实现,而非要求重写服务提供者:
php
// config/your-package.php
'driver' => env('LARAVEL_PDF_DRIVER', 'browsershot'),
'cache_profile' => App\CacheProfiles\CustomCacheProfile::class,
'hasher' => App\Hashers\CustomHasher::class,Testing Fakes with Rich Assertions
带有丰富断言的测试Fake
Provide a method on the facade that swaps in a fake builder. Track calls and offer assertion methods:
::fake()php
Pdf::fake();
// ... code that generates PDFs ...
Pdf::assertSaved(fn ($pdf, $path) => $path === 'invoice.pdf');
Pdf::assertQueued();
Pdf::assertNotQueued();在门面中提供方法,替换为假的构建器实例。跟踪调用并提供断言方法:
::fake()php
Pdf::fake();
// ... 生成PDF的代码 ...
Pdf::assertSaved(fn ($pdf, $path) => $path === 'invoice.pdf');
Pdf::assertQueued();
Pdf::assertNotQueued();Events at Key Moments
在关键节点触发事件
Fire events for important lifecycle moments so users can hook into the workflow without modifying package code.
在重要的生命周期节点触发事件,让用户无需修改包代码就能挂钩到工作流程中。
Anti-Pattern: Config Option Creep
反模式:配置选项膨胀
Don't add small config options for every customization request. Instead, give users full control via class extension.
不要为每个定制请求添加小型配置选项。相反,通过类扩展让用户获得完全控制权。
Pattern: Events Instead of Hook Config Options
模式:用事件替代钩子配置选项
Fire events and let users listen:
php
event(new TransformerStarting($transformer, $url));
$transformer->transform();
event(new TransformerEnded($transformer, $url, $result));触发事件并让用户监听:
php
event(new TransformerStarting($transformer, $url));
$transformer->transform();
event(new TransformerEnded($transformer, $url, $result));Pattern: Configurable Models
模式:可配置模型
Let users specify their own model class in config:
php
// config
'model' => Spatie\Package\Models\Result::class,
// In package code — always resolve from config:
$model = config('your-package.model');
$model::find($id);允许用户在配置中指定自己的模型类:
php
// 配置
'model' => Spatie\Package\Models\Result::class,
// 包代码中——始终从配置中解析:
$model = config('your-package.model');
$model::find($id);Pattern: Configurable Jobs
模式:可配置任务
Let users specify their own job class in config:
php
'process_job' => Spatie\Package\Jobs\ProcessJob::class,允许用户在配置中指定自己的任务类:
php
'process_job' => Spatie\Package\Jobs\ProcessJob::class,Pattern: Action Classes
模式:动作类
Wrap small pieces of functionality in action classes registered in config:
php
'actions' => [
'fetch_content' => Spatie\Package\Actions\FetchContentAction::class,
],Users override by extending and registering their custom action.
将小型功能封装到注册在配置中的动作类:
php
'actions' => [
'fetch_content' => Spatie\Package\Actions\FetchContentAction::class,
],用户可通过扩展并注册自定义动作来覆盖默认实现。
Queued Operations with Callbacks
带回调的队列操作
For expensive operations, provide that returns a wrapper around with / callbacks:
saveQueued()PendingDispatchthen()catch()php
Pdf::view('invoice', $data)
->saveQueued('invoice.pdf')
->then(fn ($path) => /* success */)
->catch(fn ($e) => /* failure */)
->onQueue('pdfs');对于耗时操作,提供方法,返回围绕的包装器,支持/回调:
saveQueued()PendingDispatchthen()catch()php
Pdf::view('invoice', $data)
->saveQueued('invoice.pdf')
->then(fn ($path) => /* 成功处理 */)
->catch(fn ($e) => /* 失败处理 */)
->onQueue('pdfs');Consistent Naming Conventions
一致的命名约定
- Suffix event classes with
Event - Suffix notification classes with
Notification - Suffix config data classes with
Config - Use for driver implementations
{Service}Driver - Use for exception classes
Could Not... - Use prefix for test doubles
Fake
- 事件类以为后缀
Event - 通知类以为后缀
Notification - 配置数据类以为后缀
Config - 驱动实现使用命名
{Service}Driver - 异常类使用命名
Could Not... - 测试替身使用前缀
Fake
Config File Comments
配置文件注释
Always add block comments above each config key or group explaining what it does:
php
return [
/*
* 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),
],
];Use block comments (not ). Mention supported values, defaults, and any non-obvious behavior. Keep comments concise — one to three lines.
/* *///始终在每个配置键或组上方添加块注释,说明其作用:
php
return [
/*
* 禁用后,中间件将不会转换任何响应。
*/
'enabled' => env('PACKAGE_ENABLED', true),
/*
* 用于执行操作的驱动。
* 支持:"local", "cloud"
*/
'driver' => env('PACKAGE_DRIVER', 'local'),
'cache' => [
/*
* 结果的缓存时长,单位为秒。
*/
'ttl' => (int) env('PACKAGE_CACHE_TTL', 3600),
],
];使用块注释(而非)。提及支持的值、默认值以及任何不明显的行为。保持注释简洁——1到3行即可。
/* *///Miscellaneous
其他注意事项
- do not add methods to migration
down - do not use else statements — return early instead
- do not use compound if statements — split into multiple ifs or use guard clauses
- use visibility over
protectedprivate
- 不要为迁移添加方法
down - 不要使用else语句——提前返回
- 不要使用复合if语句——拆分为多个if或使用守卫子句
- 优先使用可见性而非
protectedprivate