Laravel 13 包开发指南

包是 Laravel 扩展功能的重要方式。本文将介绍如何开发高质量的 Laravel 13 包。

创建包

包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
my-package/
├── src/
│ ├── MyPackage.php
│ ├── MyPackageServiceProvider.php
│ ├── Facades/
│ │ └── MyPackage.php
│ ├── Commands/
│ │ └── MyCommand.php
│ └── Contracts/
│ └── MyInterface.php
├── config/
│ └── my-package.php
├── database/
│ ├── migrations/
│ └── seeders/
├── resources/
│ └── views/
├── routes/
│ └── routes.php
├── tests/
│ └── MyPackageTest.php
├── composer.json
└── README.md

composer.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{
"name": "vendor/my-package",
"description": "A Laravel package",
"keywords": ["laravel", "package"],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Your Name",
"email": "your@email.com"
}
],
"require": {
"php": "^8.2",
"illuminate/support": "^11.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0",
"orchestra/testbench": "^9.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": "dev",
"prefer-stable": true
}

服务提供者

创建服务提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

namespace Vendor\MyPackage;

use Illuminate\Support\ServiceProvider;
use Vendor\MyPackage\Contracts\MyInterface;
use Vendor\MyPackage\Services\MyService;

class MyPackageServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(
__DIR__.'/../config/my-package.php',
'my-package'
);

$this->app->singleton(MyInterface::class, MyService::class);

$this->app->singleton('my-package', function ($app) {
return new MyPackage($app->make(MyInterface::class));
});
}

public function boot(): void
{
$this->publishes([
__DIR__.'/../config/my-package.php' => config_path('my-package.php'),
], 'config');

$this->loadMigrationsFrom(__DIR__.'/../database/migrations');

$this->loadViewsFrom(__DIR__.'/../resources/views', 'my-package');

$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/my-package'),
], 'views');

$this->loadRoutesFrom(__DIR__.'/../routes/routes.php');

if ($this->app->runningInConsole()) {
$this->commands([
Commands\MyCommand::class,
]);
}
}
}

配置文件

创建配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

return [
'default' => env('MY_PACKAGE_DEFAULT', 'value'),

'options' => [
'enabled' => true,
'timeout' => 30,
],

'connections' => [
'default' => [
'driver' => 'mysql',
'host' => env('MY_PACKAGE_DB_HOST', 'localhost'),
],
],
];

门面

创建门面

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

namespace Vendor\MyPackage\Facades;

use Illuminate\Support\Facades\Facade;

class MyPackage extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'my-package';
}
}

命令

创建 Artisan 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace Vendor\MyPackage\Commands;

use Illuminate\Console\Command;

class MyCommand extends Command
{
protected $signature = 'my-package:do-something
{--force : Force the operation}';

protected $description = 'Do something with my package';

public function handle(): int
{
$force = $this->option('force');

$this->info('Doing something...');

if ($force) {
$this->warn('Force mode enabled');
}

return self::SUCCESS;
}
}

迁移

创建迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('my_package_table', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}

public function down(): void
{
Schema::dropIfExists('my_package_table');
}
};

路由

定义路由

1
2
3
4
5
6
7
8
9
10
11
12
<?php

use Illuminate\Support\Facades\Route;
use Vendor\MyPackage\Http\Controllers\MyController;

Route::middleware(['web'])->group(function () {
Route::get('/my-package', [MyController::class, 'index'])->name('my-package.index');
});

Route::middleware(['api'])->prefix('api')->group(function () {
Route::get('/my-package', [MyController::class, 'apiIndex']);
});

控制器

创建控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace Vendor\MyPackage\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class MyController extends Controller
{
public function index()
{
return view('my-package::index');
}

public function store(Request $request)
{
return response()->json(['success' => true]);
}
}

视图

创建视图

1
2
3
4
5
6
7
8
9
<!-- resources/views/index.blade.php -->
@extends('layouts.app')

@section('content')
<div class="my-package">
<h1>My Package</h1>
<p>Welcome to my package!</p>
</div>
@endsection

中间件

创建中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace Vendor\MyPackage\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class MyMiddleware
{
public function handle(Request $request, Closure $next)
{
if (!$request->user()) {
return response()->json(['error' => 'Unauthorized'], 401);
}

return $next($request);
}
}

测试

包测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace Vendor\MyPackage\Tests;

use PHPUnit\Framework\TestCase;
use Vendor\MyPackage\MyPackage;

class MyPackageTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
}

public function test_it_can_do_something(): void
{
$package = new MyPackage();

$result = $package->doSomething();

$this->assertTrue($result);
}
}

集成测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace Vendor\MyPackage\Tests;

use Orchestra\Testbench\TestCase;
use Vendor\MyPackage\MyPackageServiceProvider;

class IntegrationTest extends TestCase
{
protected function getPackageProviders($app): array
{
return [
MyPackageServiceProvider::class,
];
}

protected function getEnvironmentSetUp($app): void
{
$app['config']->set('my-package.default', 'test');
}

public function test_config_is_loaded(): void
{
$this->assertEquals('test', config('my-package.default'));
}

public function test_facade_works(): void
{
$result = \Vendor\MyPackage\Facades\MyPackage::doSomething();

$this->assertTrue($result);
}
}

发布资源

发布配置

1
2
php artisan vendor:publish --tag=config
php artisan vendor:publish --tag=my-package-config

发布视图

1
2
php artisan vendor:publish --tag=views
php artisan vendor:publish --tag=my-package-views

发布迁移

1
2
php artisan vendor:publish --tag=migrations
php artisan vendor:publish --tag=my-package-migrations

最佳实践

1. 使用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 好的做法
interface PaymentGatewayInterface
{
public function charge(float $amount): bool;
}

class StripeGateway implements PaymentGatewayInterface
{
public function charge(float $amount): bool
{
return true;
}
}

// 在服务提供者中绑定
$this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);

2. 提供配置选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// config/my-package.php
return [
'driver' => env('MY_PACKAGE_DRIVER', 'default'),

'drivers' => [
'default' => [
'class' => Drivers\DefaultDriver::class,
],
'custom' => [
'class' => Drivers\CustomDriver::class,
],
],
];

// 在服务提供者中
$driver = config('my-package.driver');
$driverClass = config("my-package.drivers.{$driver}.class");

$this->app->bind(DriverInterface::class, $driverClass);

3. 提供文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# My Package

## Installation

composer require vendor/my-package

## Configuration

php artisan vendor:publish --tag=my-package-config

## Usage

use Vendor\MyPackage\Facades\MyPackage;

MyPackage::doSomething();

总结

Laravel 13 的包开发提供了丰富的扩展能力。通过合理使用服务提供者、门面、配置和命令,可以构建出功能完善、易于使用的 Laravel 包。记住使用接口实现依赖注入、提供灵活的配置选项、编写完善的测试,并提供清晰的文档。包开发是 Laravel 生态系统的重要组成部分,高质量的包可以帮助社区更好地发展。