開發自己的 Laravel package

前言

一直都用別人的 package,也該是時候自己寫了吧!

平常的開發總是離不開 Composer ,雖然 Laravel 的套件豐富,但還是會有需要自己開發的時候

以下會整理出如何建立出一個 Laravel package,從本機開發、自動化測試到發布

需求分析

以一個簡單的 OTP 功能為例,預期會有以下功能

  • 產生 OTP 並存到 Database
  • 檢查 OTP 是否有效

開發 Package

初始化套件

以 otp 作為範例

1
2
3
mkdir otp
cd otp
composer init

接下來會有多個問題建立套件的基本資訊,可視情況填寫
完成後會產生初始化的 composer.json 檔案

另外是要建立 Laravel 的套件,為了方便開發會需要安裝 testbench

1
composer require --dev "orchestra/testbench"
  • 專案架構
    • src
      • 通常會將 source code 放在這裡,記得要符合 PSR-4
    • tests
      • 用來存放測試相關的程式碼

功能實作

建立 OtpService 大致內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/OtpService.php

<?php

namespace Hellojie\LaravelOtp;

class OtpService
{

public function generate(): string
{
// do something...
return $token;
}

public function validate(): bool {
// do something...
return true;
}

}

測試

因為此套件會需要使用 Laravel Eloquent 進行測試,需要透過 TestBench 協助
可以建立對應的 migration file 還有 factory
TestCase 也需要做點調整

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

namespace Hellojie\LaravelOtp\Tests;

use Illuminate\Database\Eloquent\Factories\Factory;
use Orchestra\Testbench\TestCase as BaseTestCase;

class TestCase extends BaseTestCase
{
public function setUp(): void
{
parent::setUp();

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

Factory::guessFactoryNamesUsing(
fn (string $modelName) => 'Hellojie\\LaravelOtp\\Database\\Factories\\'.class_basename($modelName).'Factory'
);
}
}

設定好後就可以使用 phpunit

整合 Laravel Service Provider

為了讓 Laravel 可以方便使用套件,需要建立一個 Service Provider

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
48
// src/LaravelOtpServiceProvider.php
<?php

namespace Hellojie\LaravelOtp;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class LaravelOtpServiceProvider extends ServiceProvider implements DeferrableProvider
{

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->publishes([$this->migrationPath() => database_path('migrations')]);
}

/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton(OtpService::class, function ($app) {
return new OtpService();
});
}

/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [OtpService::class];
}

private function migrationPath()
{
return __DIR__ . '/database/migrations';
}
}

Laravel Auto Discovery

為了讓 Laravel 可以自動發現這個套件,需要在 composer.json 中進行設定,可在適當的地方加上以下內容

1
2
3
4
5
6
7
8
9
// composer.json
...
"extra": {
"laravel": {
"providers": [
"Hellojie\\LaravelOtp\\LaravelOtpServiceProvider"
]
}
}

本機測試

先建立新的 Laravel Project 進行測試,確認可不可以使用套件

1
2
3
4
5
6
cd ../
composer create-project laravel/laravel test-laravel-otp
cd test-laravel-otp

composer config repositories.laravel-otp path ../laravel-otp
composer require hellojie/laravel-otp:dev-main

安裝後可以在專案內使用指令,並根據互動提示輸入對應的數字,匯入套件內的檔案

1
php artisan vendor:publish

自動化

Github Action

利用 Github Action 自動化發起任務,可以進行自動化測試或者部署,這裡先使用自動化測試為範例,其他可視實際情況調整

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
# .github/workflows/php.yml
name: PHP Composer

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-

- name: Install dependencies
run: composer install --prefer-dist --no-progress

# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md

- name: Run test suite
run: composer run-script test

發佈

packagist

https://packagist.org/ 註冊帳號
登入後,點選右上方的 submit 開始發布套件
填入套件的 GitHub Repository url

送出後可能會看到一個提示

1
This package is not auto-updated. Please set up the GitHub Hook for Packagist so that it gets updated whenever you push!

為了讓 packagist 可以自動更新,需要到 GitHub 設定 webhook,讓 packagist 在每次更新 Repository 時都可以抓取最新資訊

步驟:

  1. 到 packagist 取的 api token
  2. 到 github repository 新增 webhook,讓套件有更新時可以自動通知 packagist 抓取

主要填寫內容如下:

1
2
3
Payload URL: https://packagist.org/api/github?username=packagist的使用者帳號
Content type: application/json
Secret: packagist 的 token

可參考 https://packagist.org/about

這時就可以透過 composer 安裝套件了

1
composer require hellojie/laravel-otp:dev-main

注意:
如果還沒有發布正式版記得要加上:dev-main

正式版本號可參考相關規範 https://semver.org/lang/zh-TW/

1
2
3
4
5
6
版本格式:主版號.次版號.修訂號,版號遞增規則如下:

主版號:當你做了不相容的 API 修改,
次版號:當你做了向下相容的功能性新增,
修訂號:當你做了向下相容的問題修正。
先行版號及版本編譯資訊可以加到「主版號.次版號.修訂號」的後面,作為延伸。

Reference

以上程式碼可以參考
https://github.com/Rjiegit/laravel-otp