Package Development
這裡以假設開發紀錄 Log 套件為例
開發流程
Git 版控前置作業(GitHub)
- 會在 GitHub 上先新增一個 repository。
- 在開發用的 Laravel 專案中直接 clone 剛剛新增的 repository 到
packages/ppcsite/record_log
🌟資料夾建議使用 packages/<my-github-username[vendor]>/<package-name>
的結構
Composer 初始化
- Terminal
cd
到剛剛新增的資料夾,透過composer init
初始化。
Terminal 指令:(若無使用版控,直接在 root
創一個資料夾)
mkdir -p packages/<my-github-username>/<package-name>
PS C:\xampp\htdocs\laravel-library> cd ./packages/ppcsite/record_log
# 初始化
PS C:\xampp\htdocs\laravel-library\packages\ppcsite\record_log> composer init
Welcome to the Composer config generator
This command will guide you through creating your composer.json config.
# 輸入套件命名
Package name (<vendor>/<name>) [user/test]: ppcsite/record_log
# 輸入套件描述
Description []: this package used for record log.
# 輸入作者訊息
Author [GavinLin512 <81368692+GavinLin512@users.noreply.github.com>, n to skip]:
# 輸入最低穩定版本,ex: stable, RC, beta, alpha, dev
Minimum Stability []: dev
# 輸入套件類型
Package Type (e.g. library, project, metapackage, composer-plugin) []: library
# 輸入授權類型
License []: MIT
Define your dependencies.
# 是否需要定義依賴套件
Would you like to define your dependencies (require) interactively [yes]? yes
# 以 php 為例
Search for a package: php
Info from https://repo.packagist.org: #StandWithUkraine
# 輸入此依賴套件最低需求版本(或留白取最新版本)
Enter the version constraint to require (or leave blank to use the latest version): >=8.0.2
# 如需多個依賴則重複上述步驟
Search for a package:
......
# 是否需要定義 require-dev
Would you like to define your dev dependencies (require-dev) interactively [yes]? no
# 是否支援 PSR-4 自動載入(對應命名空間名稱與檔案目錄名稱)
Add PSR-4 autoload mapping? Maps namespace "Packages\Test" to the entered relative path. [src/, n to skip]:
{
"name": "ppcsite/record_log",
"description": "this package used for record log",
"type": "library",
"require": {
"php": ">=8.0.2"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Ppcsite\\RecordLog\\": "src/"
}
},
"authors": [
{
"name": "GavinLin512",
"email": "81368692+GavinLin512@users.noreply.github.com"
}
],
"minimum-stability": "dev"
}
# 是否照以上資訊生成 composer.json
Do you confirm generation [yes]?
# 是否要現在安裝依賴套件
Would you like to install dependencies now [yes]? no
PSR-4 autoloading configured. Use "namespace Ppcsite\RecordLog;" in src/
Include the Composer autoloader with: require 'vendor/autoload.php';
2. 初始化完成後會自動生成 src
資料夾及 composer.json
.
├── root
│ ├── packages
│ │ ├── ppcsite
│ │ │ ├── record_log
│ │ │ │ ├── "src"
│ │ │ │ └── "composer.json"
Service Provider 相關
1. 設定 Package Discovery,讓使用者在安裝套件時自動註冊 Service Provider
與 Facade
。
關於 Service Provider
與 Facade
:
🌟透過 Service Provider
啟動 Laravel 應用程式,包括註冊 service container
綁定、EventListener
、Middleware
,甚至 Route
。
🌟Facades
在應用程式的 service container
中為 Class
提供了一個 static
介面,它們提供了一個簡潔而有力的語法來讓你使用 Laravel 的功能。
{
"extra": {
"laravel": {
"providers": [
"Ppcsite\\RecordLog\\RecordLogServiceProvider"
],
"aliases": {
"RecordLog": "Ppcsite\\RecordLog\\RecordLog"
}
}
}
}
2. 在 src
資料夾新增 Service Provider
,可以使用 php artisan make:provider XXXServiceProvider
指令新增。
.
├── record_log
│ ├── src
│ │ └── "RecordLogServiceProvider.php"
│ └── composer.json
namespace
的設定很重要,必須符合 PSR 0/PSR 4
的規範。
<?php
namespace Ppcsite\RecordLog;
use Illuminate\Support\ServiceProvider;
class RecordLogServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
// 定義需要註冊的服務
// 讓 service container 連結自定義的 Facades
$this->app->bind('packages-record-log', function($app) {
return new \Ppcsite\RecordLog\Models\AdminLog();
});
// 合併使用者與系統預設 config 設定檔,讓使用者可以合併覆寫 config 設定檔
$this->mergeConfigFrom(
__DIR__.'/../config/record-log.php', 'record-log'
);
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot(): void
{
// 發佈 config 設定檔
$this->publishes([
__DIR__.'/../config/record-log.php' => config_path('record-log.php'),
],'record-log-config');
// 發佈 migration
$timestamp = date('Y_m_d_His');
$this->publishes([
__DIR__.'/../database/migrations/create_admin_logs_table.php' => database_path('migrations/'.$timestamp.'_create_admin_logs_table.php'),
],'record-admin-logs-migration');
}
}
3. 在 src
資料夾新增 Facades/RecordLog.php
,負責綁定套件的依賴注入方法。
.
├── record_log
│ ├── src
│ │ ├── Facades
│ │ │ └── "RecordLog.php"
│ │ └── RecordLogServiceProvider.php
│ └── composer.json
<?php
namespace Ppcsite\RecordLog\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \Ppcsite\RecordLog\RecordLog
*/
class RecordLog extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*
*/
protected static function getFacadeAccessor():string
{
return 'packages-record-log';
}
}
套件內部相關
🌟資料夾結構範例:
├── src
│ ├── Facades
│ ├── Contracts(interface)
│ ├── Models
│ ├── Views
│ ├── Commands
│ ├── Middlewares
│ ├── Exceptions
│ └── Traits
├── database
│ ├── factories
│ ├── seeders
│ └── migrations
├── resourses
│ └── views
├── config
└── tests
1. 在 src
資料夾新增 Contracts/AdminLog.php
,定義 Class
的使用介面 interface
。
.
├── record_log
│ ├── src
│ │ ├── Facades
│ │ │ └── RecordLog.php
│ │ ├── Contracts
│ │ │ └── "AdminLog.php"
│ │ └── RecordLogServiceProvider.php
│ └── composer.json
<?php
namespace Ppcsite\RecordLog\Contracts;
use Illuminate\Database\Eloquent\Model;
interface AdminLog
{
/**
* 紀錄 Log
*
* @param string $function_name
* @param $action
* @param Model|null $data_model
* @param $comment
* @return void
*/
public static function Log(string $function_name, string $action, Model $data_model=null, string $comment=null): void;
/**
* 取得變更資料之 model 關聯
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function datatable(): \Illuminate\Database\Eloquent\Relations\MorphTo;
}
2. 在 src
資料夾新增 Models/AdminLog.php
,定義此套件的核心 Model。
.
├── record_log
│ ├── src
│ │ ├── Facades
│ │ │ └── RecordLog.php
│ │ ├── Contracts
│ │ │ └── AdminLog.php
│ │ ├── Models
│ │ │ └── "AdminLog.php"
│ │ └── RecordLogServiceProvider.php
│ └── composer.json
<?php
namespace Ppcsite\RecordLog\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
class AdminLog extends Model implements \Ppcsite\RecordLog\Contracts\AdminLog
{
// 操作類型
CONST ACTION_NEW = '新增';
CONST ACTION_EDIT = '編輯';
CONST ACTION_DELETE = '刪除';
CONST ACTION_SEARCH = '查詢';
CONST ACTION_POST = '送出';
CONST ACTION_DOWNLOAD = '下載';
CONST ACTION_UPLOAD = '上傳';
CONST ACTION_MAIL = '發信';
CONST ACTION_LOGIN = '登入';
CONST ACTION_LOGOUT = '登出';
CONST ACTION_REGISTER = '註冊';
protected $guarded = [];
public function datatable(): \Illuminate\Database\Eloquent\Relations\MorphTo
{
return $this->morphTo();
}
public static function Log(string $function_name, string $action, Model $data_model=null, string $comment=null): void
{
$ip = Request::ip();
$log = new self([
'function_name' => $function_name,
'user_id' => Auth::user()->id??0,
// 'user_type' => $user_type??'user',
'action' => $action,
'comment' => $comment,
'ip' => $ip,
]);
if($data_model) {
$log->datatable()->associate($data_model);
}
$log->save();
}
}
3. 在開發過程中如果要方便直接測試套件,需照以下步驟:
- 在原根目錄
composer.json
設定autoload
資料夾為src
。
🌟 Autoload
為 PHP
自動載入的機制,可以讓我們能夠在需要這個物件的時候才去真正的引入這個 Class
。
{
"autoload": {
"psr-4": {
"Ppcsite\\RecordLog\\": "src/"
}
}
}
- 在
config\app
中加入Package Service Providers
及Class Aliases
。
[
'providers' => [
...
/*
* Package Service Providers...
*/
Ppcsite\RecordLog\RecordLogServiceProvider::class,
...
],
'aliases' => Facade::defaultAliases()->merge([
// 'ExampleClass' => App\Example\ExampleClass::class,
'RecordLog' => Ppcsite\RecordLog\Facades\RecordLog::class,
])->toArray(),
]
- 在 Terminal 輸入
composer dump-autoload
。
套件發布
1. 以 Packagist 為例 (public)
到 Packagist 使用 GitHub 登入帳號,直接輸入 repository 的網址,然後 Submit Package 就完成了。
2. 以 Gitea 為例 (private)
將該套件資料夾打包成一個 .zip
檔,透過 postman 傳送一個 PUT
操作,將檔案傳到 Gitea Api 網址底下,範例如下:
https://gitea.example.com/api/packages/{owner}/composer
打包時注意不要將 vendor
資料夾也打包進去。
🌟傳送資料時,在 body 中選擇 binary,即可選擇打包後的檔案上傳。
🔆也可以加上版本資訊:
https://gitea.example.com/api/packages/{owner}/composer?version={x.y.z}
回傳結果如下:
HTTP 狀態代碼 | 意義 |
---|---|
201 Created | 此套件已經被發布. |
400 Bad Request | 此套件的名稱 及/或 版本是無效的,或是已存在與此套件相同名稱及版本的套件. |
📢設定套件登錄
需加入以下內容到本機 Composer 的 config.json
內,通常位置會在 <user-home-dir>/.composer/config.json
:
{
"repositories": [{
"type": "composer",
"url": "https://gitea.example.com/api/packages/{owner}/composer"
}
]
}
若是使用身分驗證設定套件登錄,則需加入以下程式碼到 auth.json
:
{
"http-basic": {
"gitea.example.com": {
"username": "{username}",
"password": "{password}"
}
}
}
若是在本機 Composer 資料夾找不到 config.json
及 auth.json
,請先輸入以下指令創建:
composer config --global
🌟欄位說明如下:
參數名 | 說明 |
---|---|
owner | 套件的擁有者 |
username | Gitea 的使用者名稱 |
password | Gitea 的使用者密碼或是個人 token |
套件安裝方法:
composer require {package_name}
或是根據指定版本安裝:
composer require {package_name}:{package_version}
參考連結
composer 相關
- composer 官方文件
- composer 中文文檔 (repositories)
- composer init 命令詳解
- Composer symlink for packages
- PHP內建自動載入autoload方式跟常誤會一些觀念
- PHP系列 - Autoload 自動載入
- 發佈 Composer 套件的事前準備
- PHP-Composer Package 開發、發布流程
授權相關
架構相關
Laravel 相關
- Laravel Package Development
- Creating Your Own Laravel Custom Package: A Step-by-Step Guide
- How to create your own Laravel packages
- Laravel Package Development with Local Composer Dependencies
- Laravel create packages
- 關於 Facade 如何運作
套件發布相關