開發屬於自己的 Laravel 套件
介紹如何在本機進行 Laravel 套件開發及撰寫測試,並在本機專案透過 composer 安裝自行開發的套件
目前的 Laravel 生態中有許多好用的套件包可以使用
但要是找不到別人寫好的套件包,又或是必須建置公司共用的套件包怎麼辦呢?
本篇就來在本機進行套件包開發,並且會在本機專案中使用 composer 進行安裝並測試
有些文章會教你在既有的 Laravel 專案中建立 packages 資料夾,並在裡面進行開發
但這不是一個好方法,試想你萬一有天把專案刪掉了,套件包的專案也被刪掉了
並且這種做法不能測試使用 composer 安裝是否正常,所以本篇使用的是另一種方式
1. 初始化套件專案
首先我們開一個資料夾,方便起見我們用 larapeko
作為範例
mkdir larapeko
接著進到剛剛的 larapeko 資料夾中,執行指令初始化套件
cd larapeko
composer init
會經過一連串的問答協助你設定套件的基本資訊
接著就會在資料夾內看到剛剛初始化的套件 composer.json 檔案
{
"name": "ray247k/larapeko",
"description": "A package for demo peko",
"license": "MIT",
"authors": [
{
"name": "Ray",
"email": "[email protected]"
}
],
"minimum-stability": "dev",
"require": {}
}
2. 撰寫套件程式
在 larapeko 資料夾中新建 src 資料夾,我們的套件程式碼主要都放在這
建立一個名為 LaraPeko.php 的檔案,並設定所使用的 namespace
為了後續示範,我先建立一個簡單的 function
<?php
# LaraPeko.php
namespace Ray247k\LaraPeko;
class LaraPeko
{
public function sayPeko()
{
echo "好油喔 peko\n";
}
}
3. ServiceProvider
在 src 目錄內建立剛剛檔案的 ServiceProvider,為了好識別,我們就命名為 LaraPekoServiceProvider.php
透過 register 中的 singleton,會註冊一個叫做LaraPeko
的類別,並回傳剛剛建立的 LaraPeko
物件
<?php
# LaraPekoServiceProvider.php
namespace Ray247k\LaraPeko;
use Illuminate\Support\ServiceProvider;
class LaraPekoServiceProvider extends ServiceProvider
{
public function boot()
{
}
// 註冊套件函式
public function register()
{
$this->app->singleton('LaraPeko', function ($app) {
return new LaraPeko();
});
}
}
4. Facade
Facade 提供一個靜態的介面讓我們直接呼叫註冊過的類別
在 src 目錄內建立剛剛檔案的 Facade,命名為 LaraPekoFacade.php 吧
使用剛剛上面 ServiceProvider 註冊的LaraPeko
類別作為 Laravel 的 Facade 物件
<?php
# LaraPekoFacade.php
namespace Ray247k\LaraPeko;
use Illuminate\Support\Facades\Facade;
class LaraPekoFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'LaraPeko';
}
}
5. 預設 config 以及使用方法
在使用套件的時候,有時候會想讓使用者可以透過設定檔設定參數
這時候可以在套件目錄下建立config
資料夾,並且在裡面加入 config 設定檔 lara_peko.php
<?php
# lara_peko.php
return [
'best_girl' => 'Yagoo',
];
建立好了之後,我們要編輯LaraPekoServiceProvider.php
成底下內容
<?php
# LaraPekoServiceProvider.php
namespace Ray247k\LaraPeko;
use Illuminate\Support\ServiceProvider;
class LaraPekoServiceProvider extends ServiceProvider
{
public function boot()
{
$source = realpath($raw = __DIR__.'/../config/lara_peko.php') ?: $raw;
$this->publishes([
$source => config_path('lara_peko.php'),
]);
}
// 註冊套件函式
public function register()
{
$configPath = __DIR__ . '/../config/lara_peko.php';
$this->mergeConfigFrom($configPath, 'lara_peko');
$this->app->singleton('LaraPeko', function ($app) {
return new LaraPeko();
});
}
}
在boot()
中使用publishes
方法後,在引用套件的時候可以在專案內使用指令
php artisan vendor:publish
並根據跳出的互動視窗選擇套件對應數字輸入
就可以的在專案目錄的 config 路徑下產生套件包的 config 範本對應的檔案
如果如果你很確定自己要建立的是哪一個套件的 config 檔,也可以直接加上 provider 標籤
指定要呼叫的是哪一個命名空間內的 provider 的 publishes 發布方法
php artisan vendor:publish --provider="Ray247k\LaraPeko\LaraPekoServiceProvider"
而register()
中則是將預設的 config 設定檔和新建立的設定檔合併
如此一來,若使用者對專案設定檔做修改,會覆蓋掉預設的設定檔,達到客製化的目的
清除 config 的快取
當 config 有變更,則需要使用指令清除專案內的設定檔快取
php artisan config:clear # 清除設定檔快取
php artisan cache:clear # 清除一般快取
使用 config 參數
若是要在程式中使用 config 設定,則可以使用 config()
方法呼叫
回到LaraPeko.php
,新建個方法getBestGirl
取得設定檔中的best_girl
參數內容
<?php
# LaraPeko.php
namespace Ray247k\LaraPeko;
class LaraPeko
{
public function sayPeko()
{
echo "好油喔 peko\n";
}
public function getBestGirl()
{
echo config('lara_peko.best_girl');
}
}
6. 套件包自動發現
Laravel 5.5 新增的功能,可以借由套件包的設定減少使用者安裝時候需要的步驟
目的是在執行 composer install
之後,不需要手動編輯config/app.php
的providers
和aliases
陣列
關鍵字:Package Auto-discovery、Laravel 擴充套件包自動發現
作法:
在套件包的 composer.json 裡面加入 autoload
和 extra
的內容
因為整串貼有點太長,就只貼有新增的部分
{
"autoload": {
"psr-4": {
"Ray\\LaraPeko\\": "src"
}
},
"extra": {
"laravel": {
"providers": [
"Ray\\LaraPeko\\LaraPekoServiceProvider"
],
"aliases": {
"LaraPeko": "Ray\\LaraPeko\\LaraPekoFacade"
}
}
}
}
藉由 extra 中的 laravel 定義,會在陣列中的 providers 和 aliases 內各加入一筆資料進行相關註冊
而 autoload 會使用 psr-4 的規則將指定命名空間指向我們套件程式所在的資料夾src
7. 套件包相依套件
如果你的套件包相依於某個套件,例如常用的Guzzle
那麼就在套件包的 composer.json 中加入 require 的項目如下
{
"name": "ray247k/larapeko",
"description": "A package for demo peko",
"license": "MIT",
"authors": [
{
"name": "Ray",
"email": "[email protected]"
}
],
"minimum-stability": "dev",
"require": {
"php": ">=7.3",
"laravel/framework": "5.5.*||^6.0|^7.0|^8.0",
"guzzlehttp/guzzle": "^6.3"
}
}
我們除了 require guzzle 外,還 require 了 php 必須大於指定版本以及 Laravel
8. 套件包測試
建立測試用專案
經過上面流程,我們的套件包差不多就設定好了
接著就要進入測試流程,因為我們是要在 Laravel 專案中使用的
所以就建立一個新的 Laravel 專案來進行測試,叫做 lara-ahoy
laravel new lara-ahoy
可以藉由指令查看安裝的 Laravel 版本
cd <專案目錄>
php artisan -V
# Laravel Framework 6.20.30
專案中引用套件
加入套件項目
在專案中使用指令,在 composer.json 中加入套件的版本儲存位置
這邊因為是在本機測試,所以使用 path 方法指定本機套件包的相對或絕對路徑
關於路徑的設定可以參考官方說明:Composer Documentation #Path
composer config repositories.ray247k path ../larapeko
從相對路徑看得出來我把套件包(larapeko) 和測試用的專案(lara-ahoy) 放在同一個資料夾裡面
指令執行之後可以打開 lara-ahoy 專案的 composer.json 檔案
會發現最下面多了一段
{
"repositories": {
"ray247k": {
"type": "path",
"url": "../larapeko"
}
}
}
如果不想用指令加入套件位址,也可以手動加入這段
如果套件是在某個 repository 的話
可以把引用 type 換成 vcs
,然後加上遠端版本庫的 url
composer config repositories.ray247k vcs https://github.com/ray247k/larapeko
或是手動調整 composer.json 裡面的 repositories 設定
{
"repositories": {
"ray247k": {
"type": "vcs",
"url": "https://github.com/ray247k/larapeko"
}
}
}
遠端的 url 可以使用 https 或是 ssh 方法
但如果該套件的 repository 是私有的,那就必須用 ssh:[email protected]:ray247k/larapeko.git
添加依賴
可以使用指令
composer require ray247k/larapeko @dev
或是直接打開 cmoposer.json 找到require
段落加入底下內容
{
"require": {
"ray247k/larapeko": "@dev"
},
}
若是在使用指令的時候發生某個套件的相依套件無法安裝而造成錯誤的話
Problem 1
- <某個套件> is locked to version 3.1.2 and an update of this package was not requested.
- <某個套件> 3.1.2 requires ext-rdkafka >=1.0 -> it is missing from your system. Install or enable PHP's rdkafka extension.
可以藉由加上 --ignore-platform-reqs
忽略平台
composer require ray247k/larapeko --ignore-platform-reqs
在添加本機依賴之後,預設每次被使用都會自動去抓取本機套件最新的程式碼
如果沒自動抓取,或是不想要自動抓取,而是每次都想要 composer require 來更新的話
可以在測試專案中的 composer.json 透過 repositories 區塊內的 options.symlink 設定來調整
{
"repositories": [
{
"type": "path",
"url": "../../packages/my-package",
"options": {
"symlink": false
}
}
]
}
安裝套件
使用套件安裝指令
composer install
在上圖中可以看到剛剛的套件已經成功被發現,並且安裝
Discovered Package: ray247k/larapeko
Package manifest generated successfully.
如果跟 require 的時候一樣發生相依套件的版本錯誤,一樣可以加入參數忽略平台
composer install --ignore-platform-reqs
打開 lara-ahoy 中的 composer.lock
檔案,可以看到剛剛成功安裝的套件資料
若是先前已經執行過專案,那必須清除原有 autoload 的快取
composer dump-autoload
測試套件
上一步驟中我們在測試用專案 lara-ahoy 中安裝好了本機開發的套件 larapeko
接著會分別用兩種方式測試套件是否有正確被引入,分別是直接呼叫測試和單元測試
在開發套件時候如果就有撰寫單元測試,那這時候就會方便許多
本篇文章也會帶著你建立基本的單元測試並執行,就繼續看下去吧!
方法一、檔案測試
直接在測試用的 Laravel 專案跟目錄資料夾下建立 test-autoload.php 檔案
在檔案中載入 autoload 之後呼叫套件方法
<?php
# test-autoload.php
use Ray247k\LaraPeko\LaraPeko;
require_once './vendor/autoload.php';
LaraPeko::sayPeko();
在 lara-ahoy 專案目錄下執行測試檔案
php test-autoload.php
如果成功載入應該會出現套件中sayPeko()
的執行結果
雖然跳出了 PHP Deprecated,但是還是有成功印出我們的預設字串內容
代表剛剛在 ServiceProvider 註冊的 Facade aliases 有成功被呼叫了!
如果真的很不想看到那個錯誤訊息
只要在 test-autoload.php 中加入error_reporting(0);
關閉錯誤訊息提示
<?php
error_reporting(0);
use Ray247k\LaraPeko\LaraPeko;
require_once './vendor/autoload.php';
LaraPeko::sayPeko();
如此就不會看到 PHP Deprecated 的提示了
方法二、使用單元測試
接著就是使用單元測試來測試套件,參考文件:Laravel Package Development #Testing
先試試看 PHPUnit 是否可以執行,在專案目錄下使用指令
php vendor/bin/phpunit
如果出現底下錯誤訊息
/usr/bin/php declares an invalid value for PHP_VERSION.
This breaks fundamental functionality such as version_compare().
Please use a different PHP interpreter.
那看來是蘋果的鍋:PHPUnit does not work when I run the tests on the Laravel framework
PHPUnit refuses to be run with a PHP interpreter where the value of the PHP_VERSION constant contains an invalid value due to modification made by vendors that ship (binary) distributions of PHP.
7.3.24-(to be removed in future macOS) is such an invalid value. The -(to be removed in future macOS) suffix was added by Apple, who is the vendor of the PHP interpreter binary you use.
TL;DR: Do not use the PHP interpreter that is shipped by Apple with macOS. Use Homebrew, or similar, instead.
這時候有兩個解法,一個是用 Homebrew
安裝 PHP,記得要修改 bash/zsh 的設定檔
另一個是把測試專案搬到 docker 環境裡面 PHP mount 的資料夾內
直接進到 php 容器裡面執行 php vendor/bin/phpunit 指令
如果 php vendor/bin/phpunit
可以執行,接著就要來建置單元測試檔案
先在 LaraPeko.php
新增一個測試用的 function
public function getAhoy()
{
return "ahoy";
}
接著在套件包專案內建立 tests 資料夾,並建立測試檔案 LaraPekoTest.php
<?php
namespace Ray247k\LaraPeko;
use PHPUnit\Framework\TestCase;
class LaraPekoTest extends TestCase
{
/**
* @test
*
* @return void
*/
public function testClassInstance()
{
$this->assertInstanceOf(LaraPekoTest::class, new LaraPekoTest);
}
public function testGetAhoy()
{
$larapeko = new LaraPeko();
$this->assertEquals('ahoy', $larapeko->getAhoy());
}
}
可以看到我們呼叫 LaraPeko 物件,然後調用了 getAhoy()
方法
因為我們上面有建立了這個方法,會回傳「ahoy」
使用 assertEquals
的測試斷言式判斷回傳值是不是等於「ahoy」
建立好 test file 之後進到容器中的 Laravel 專案的根目錄,使用指令執行指定路徑下的測試
php vendor/bin/phpunit vendor/ray247k/larapeko/tests
補充
假設現在 LaraPeko 套件裡有個 private function 叫做 testMethod
private function testMethod()
{
return 'Haha';
}
如果想測試 private function 可以使用閉包中的 bindTo() 方法
產生物件之後手動注入一個物件,替換掉 closure 物件中的 $this
如此一來就像是直接使用 LaraPeko 物件去呼叫 testMethod 方法
/**
* @test
*
* @return void
*/
public function testMethodTest()
{
$contentSegment = new LaraPeko();
$closure = function () {
return $this->testMethod();
};
$closure_bind = $closure->bindTo($contentSegment, $contentSegment);
$this->assertEquals('Haha', $closure_bind());
}
這樣去執行單元測試就可以對 private 方法進行測試了!
以上就是這次開發屬於自己的 Laravel 套件的筆記內容