您好,登錄后才能下訂單哦!
這篇“怎么構建一個自己的Laravel包”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“怎么構建一個自己的Laravel包”文章吧。
使用您的包名稱創建一個新目錄,然后在您選擇的代碼編輯器中打開它,以便我們開始設置。 我對任何新包做的第一件事是將其初始化為 git 存儲庫,因此運行以下 git 命令:
git init
現在我們有了一個可以使用的存儲庫,我們將能夠將內容提交到歷史版本,并允許在適當的時候對包進行版本控制。
創建一個 PHP 包需要馬上做一件事:一個 composer.json
文件,它會告訴 Packagist 這個包是什么以及它需要運行什么。你可以使用命令行 Composer 工具或手動創建 Composer 文件。我通常使用命令行 composer init
,因為它是一種交互式的設置方式;但是,我將顯示我的 Composer 文件開頭的輸出,以便你可以看到結果:
{ "name": "juststeveking/laravel-data-object-tools", "description": "A set of tools to make working with Data Transfer Objects easier in Laravel", "type": "library", "license": "MIT", "authors": [ { "role": "Developer", "name": "Steve McDougall", "email": "juststevemcd@gmail.com", "homepage": "https://www.juststeveking.uk/" } ], "autoload": { "psr-4": { "JustSteveKing\\DataObjects\\": "src/" } }, "autoload-dev": { "psr-4": { "JustSteveKing\\DataObjects\\Tests\\": "tests/" } }, "require": { "php": "^8.1" }, "require-dev": {}, "minimum-stability": "dev", "prefer-stable": true, "config": { "sort-packages": true, "preferred-install": "dist", "optimize-autoloader": true } }
這是我的大多數包的基礎結構,無論是 Laravel 還是普通的 PHP 包,它以一種我已知并保持風格一致的方式進行設置。我們需要在包中添加一些支持文件才能開始。首先,我們需要添加
.gitignore
文件,這樣我們就可以告訴版本控制我們不想提交哪些文件和目錄:
這是我們要忽略的文件的開始。我正在使用 PHPStorm,它將添加一個名為 創建版本時,我們會告訴源代碼控制提供者我們想要忽略哪些文件以及如何處理差異。最后,我們的最后一個支持文件將是 現在我們有了版本控制的支持文件和編輯器,我們可以開始考慮我們的包在依賴關系方面需要什么。我們的包將依賴哪些依賴項,以及我們使用哪些版本?讓我們開始吧。/vendor/
/.idea
composer.lock
.idea
的元目錄,其中包含我的 IDE 理解我的項目所需的所有信息——我不想提交版本控制。接下來,我們需要添加一些 git 的屬性配置,以便版本控制知道如何處理我們的存儲庫。這稱為.gitattributes
:* text=auto
*.md diff=markdown
*.php diff=php
/.github export-ignore
/tests export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
CHANGELOG.md export-ignore
phpunit.xml export-ignore
.editorconfig
,該文件告訴我們的代碼編輯器如何處理我們正在編寫的文件:root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml,json}]
indent_size = 2
當我們正在構建一個 Laravel 包時,我們首先需要的是 Laravel 支持包,所以使用以下 composer 命令安裝它:
composer require illuminate/support
現在可以著手做一些事情,來看一下包需要的代碼的第一個重要部分:服務提供者。服務提供者是所有 Laravel 包的關鍵部分,因為它告訴 Laravel 如何加載包以及可用的包。首先,我們想讓 Laravel 知道我們有一個安裝后可以使用的控制臺命令。我已經調用了我的服務提供商
PackageServiceProvider
,因為我想象力有限,而且不會起名。如果您愿意,請隨意更改您自己的命名。我在 src/Providers
下添加了我的服務提供商,因為它熟悉 Laravel 應用程序。
我通常將我知道不希望擴展的類作為最終類,因為這樣做會改變我希望包的操作方式。你不需要這樣做。這是你需要為自己做出的判斷。所以我們現在注冊了一個命令。我們應該考慮創建它。從命名中可以看出,它是一個將為我們生成其他類的命令——與典型的工匠命令略有不同。declare(strict_types=1);
namespace JustSteveKing\DataObjects\Providers;
use Illuminate\Support\ServiceProvider;
use JustSteveKing\DataObjects\Console\Commands\DataTransferObjectMakeCommand;
final class PackageServiceProvider extends ServiceProvider
{
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->commands(
commands: [
DataTransferObjectMakeCommand::class,
],
);
}
}
}
我創建了一個名為 DataTransferObjectMakeCommand
的類,它非常冗長,但解釋了它在 src/Console/Commands
內部的作用。如你所見,在創建這些類時,我嘗試反映 Laravel 開發人員熟悉的目錄結構。這樣做會使使用包變得更加容易。讓我們看一下這個命令的代碼:
declare(strict_types=1); namespace JustSteveKing\DataObjects\Console\Commands; use Illuminate\Console\GeneratorCommand; use Illuminate\Support\Str; final class DataTransferObjectMakeCommand extends GeneratorCommand { protected $signature = "make:dto {name : The DTO Name}"; protected $description = "Create a new DTO"; protected $type = 'Data Transfer Object'; protected function getStub(): string { $readonly = Str::contains( haystack: PHP_VERSION, needles: '8.2', ); $file = $readonly ? 'dto-82.stub' : 'dto.stub'; return __DIR__ . "/../../../stubs/{$file}"; } protected function getDefaultNamespace($rootNamespace): string { return "{$rootNamespace}\\DataObjects"; } }
讓我們通過這個命令來了解我們正在創建什么。我們的命令想要擴展
GeneratorCommand
,因為我們想要生成一個新文件。理解這一點很有用,因為幾乎沒有關于如何做到這一點的文檔。對于這個命令,我們唯一需要的是一個名為 getStub
的方法--該命令需要知道如何加載存根文件的位置以幫助生成文件。我在包的根目錄中創建了一個名為 stubs
的目錄,這是 Laravel 應用程序熟悉的地方。您將在這里看到我正在檢查已安裝的 PHP 版本,以查看我們是否使用 PHP 8.2,如果是 - 我們希望加載正確的存根版本以利用只讀類。現在發生這種情況的可能性非常低 - 但是,我們離我們并不遙遠。這種方法有助于為特定的 PHP 版本生成文件,因此您可以確保支持您希望支持的每個版本。
最后,我已經為我的 DTO 設置了默認命名空間,所以我知道我希望它們放在哪里。畢竟我不想過度填充根命名空間。
先來快速了解一下這些存根文件,默認的命名空間為 stub:
<?php declare(strict_types=1); namespace {{ namespace }}; use JustSteveKing\DataObjects\Contracts\DataObjectContract; final class {{ class }} implements DataObjectContract { public function __construct( // ) {} public function toArray(): array { return []; } }
我們的 DTO 將實施一個契約來保證一致性——我喜歡盡可能多地使用這些類。此外,我們的 DTO 類是 final 類。我們可能不想擴展這個類,所以默認情況下將其設為 final 是一種明智的做法。現在讓我們看一下 PHP 8.2 版本:
這里唯一的區別是我們將 DTO 類設為只讀以利用該語言的新特性。<?php
declare(strict_types=1);
namespace {{ namespace }};
use JustSteveKing\DataObjects\Contracts\DataObjectContract;
readonly class {{ class }} implements DataObjectContract
{
public function __construct(
//
) {}
public function toArray(): array
{
return [];
}
}
我們如何測試這個?首先,我們要安裝一個測試包,以確保我們可以編寫運行此命令的測試 - 我將為此使用 pestPHP,使用 PHPUnit 將可以以非常相似的方式工作。
composer require pestphp/pest --dev --with-all-dependencies
此命令將要求您允許 Pest 使用 Composer 插件,因此如果您需要 Pest 插件進行測試(例如并行測試),請確保您對此表示同意。接下來,我們需要一個允許我們在測試中使用 Laravel 的包,以確保我們的包有效地工作。這個包叫做 Testbench,是我在構建 Laravel 包時使用的。
在我們的包中初始化測試套件的最簡單方法是使用 pesPHP 為我們初始化它。運行以下控制臺命令: 這將生成 composer require --dev orchestra/testbench
./vendor/bin/pest --init
phpunit.xml
文件和一個 tests/Pest.php
文件,用于控制和擴展 pest。首先,我喜歡對 Pest 要使用的 PHPUnit 配置文件進行一些更改。我喜歡添加以下選項以使我的測試更容易:
stopOnFailure
我設置為 truecacheResults
我設置為 false
我這樣做是因為如果測試失敗,我想立即知道。越早的返回和失敗有助于我們構建更有信心的東西。緩存結果可以加速你的包的測試。但是,我喜歡確保每次都從頭開始運行我的測試套件,以確保它按我的預期工作。
現在讓我們將注意力集中在一個默認測試用例上,我們需要我們的包測試來運行它。在 tests/PackageTestCase.php
下創建一個新文件,這樣我們就可以更輕松地控制我們的測試。
declare(strict_types=1); namespace JustSteveKing\DataObjects\Tests; use JustSteveKing\DataObjects\Providers\PackageServiceProvider; use Orchestra\Testbench\TestCase; class PackageTestCase extends TestCase { protected function getPackageProviders($app): array { return [ PackageServiceProvider::class, ]; } }
PackageTestCase
擴展了測試平臺TestCase
,因此我們可以從包中借用行為來構建我們的測試套件。然后我們注冊我們的包服務提供者,以確保我們的包被加載到測試應用程序中。
現在讓我們看看如何測試它。在我們編寫測試之前,我們要確保我們測試的內容涵蓋了包的當前行為。到目前為止,我們的測試所做的只是提供一個命令,可以運行該命令來創建一個新文件。我們的測試目錄結構將反映我們的包結構,所以在 tests/Console/Commands/DataTransferObjectMakeCommandTest.php
下創建我們的第一個測試文件,然后開始我們的第一個測試。
在我們編寫第一個測試之前,我們需要編輯 tests/Pest.php
文件以確保我們的測試套件正確使用我們的 PackageTestCase
。
declare(strict_types=1); use JustSteveKing\DataObjects\Tests\PackageTestCase; uses(PackageTestCase::class)->in(__DIR__);
首先,要確保我們的命令可以運行并且運行成功。所以添加以下測試:
我們正在測試當我們調用這個命令時,運行沒有錯誤。如果您問我,這是最關鍵的測試之一,如果它出錯,則意味著出現問題。declare(strict_types=1);
use JustSteveKing\DataObjects\Console\Commands\DataTransferObjectMakeCommand;
use function PHPUnit\Framework\assertTrue;
it('can run the command successfully', function () {
$this
->artisan(DataTransferObjectMakeCommand::class, ['name' => 'Test'])
->assertSuccessful();
});
既然我們知道我們的測試可以運行,我們還想確保創建了類。所以讓我們接下來編寫這個測試:
declare(strict_types=1); use Illuminate\Support\Facades\File; use JustSteveKing\DataObjects\Console\Commands\DataTransferObjectMakeCommand; use function PHPUnit\Framework\assertTrue; it('create the data transfer object when called', function (string $class) { $this->artisan( DataTransferObjectMakeCommand::class, ['name' => $class], )->assertSuccessful(); assertTrue( File::exists( path: app_path("DataObjects/$class.php"), ), ); })->with('classes');
這里我們使用 Pest Dataset 來運行一些選項,有點像 PHPUnit Data Provider。我們遍歷每個選項并調用我們的命令,斷言文件存在。我們現在知道可以將名稱傳遞給我們的 artisan 命令并創建一個 DTO 供我們在應用程序中使用。
最后,我們想為我們的包構建一個 facade,以允許我們的 DTO 輕松水合。擁有 DTO 通常只是成功的一半,是的,我們可以向 DTO 本身添加一個方法來靜態調用 - 但我們可以大大簡化這個過程。我們將通過 Frank de Jonge 在他的 Eventsauce 包 中使用一個非常有用的包來促進這一點,稱為「對象保濕劑」。請運行以下 composer 命令安裝它:
composer require eventsauce/object-hydrator
是時候圍繞這個包構建一個包裝器,以便我們可以很好地使用它,所以讓我們在
src/Hydrator/Hydrate.php
下創建一個新類,如果需要,我們還將創建一個契約在任何時候交換實現。這將是src/Contracts/HydratorContract.php
。讓我們從契約開始,了解我們想要它做什么。
我們所需要的只是一種水合對象的方法,因此我們使用對象的類名和一組屬性來返回一個數據對象。現在讓我們看一下實現: 我們有一個對象映射器傳遞給構造函數或在構造函數中創建 - 然后我們在填充方法中使用它。然后填充方法使用映射器來水合對象。它使用簡單干凈,如果我們將來選擇使用不同的保濕器,可以輕松復制。但是,使用這種方式,我們希望將水化器綁定到容器中,以允許我們使用依賴注入來解決它。將以下內容添加到 現在我們有了 hydrator,我們需要創建一個 facade,以便我們可以在我們的應用程序中很好地調用它。現在讓我們在 所以我們的外觀當前返回的是 Hydrator 的事件實現-這意味著我們無法從容器中解決這個問題,所以如果我們切換實現,我們將需要更改 facade。不過,就目前而言,這還不是什么大事。接下來,我們需要將此別名添加到我們的文件中,以便 Laravel 在我們安裝軟件包時知道它。 現在我們已經注冊了 Facade,我們需要測試它是否按預期工作。讓我們來看看如何測試它。在 我們創建了一個名為 strings 的新數據集,它返回一個隨機字符串數組供我們使用。我們將它傳遞給我們的測試并嘗試在我們的 facade 上調用填充方法。傳入一個測試類,我們可以創建一組屬性來進行水合。然后,當我們在 DTO 上調用 我們現在可以確定我們的包按預期工作。我們需要做的最后一件事是關注代碼的質量。在我的大多數包中,我喜歡確保編碼風格和靜態分析都在運行,這樣我就有了一個值得信賴的可靠包。讓我們從代碼樣式開始。為此,我們將安裝一個名為 Laravel Pint 的相對較新的軟件包: 我喜歡使用 PSR-12 作為我的代碼風格,所以讓我們在包的根目錄中創建一個 現在運行 pint 命令來修復任何不符合 PSR-12 的代碼樣式問題: 最后,我們可以安裝 PHPStan,這樣我們就可以靜態分析我們的代碼,以確保我們盡可能嚴格并與我們的類型保持一致: 要配置 PHPStan,我們需要在包的根目錄中創建一個 最后,我們可以運行 PHPStan 來分析我們的代碼 如果一切順利,我們現在應該會看到 declare(strict_types=1);
namespace JustSteveKing\DataObjects\Contracts;
interface HydratorContract
{
/**
* @param class-string<DataObjectContract> $class
* @param array $properties
* @return DataObjectContract
*/
public function fill(string $class, array $properties): DataObjectContract;
}
declare(strict_types=1);
namespace JustSteveKing\DataObjects\Hydrator;
use EventSauce\ObjectHydrator\ObjectMapperUsingReflection;
use JustSteveKing\DataObjects\Contracts\DataObjectContract;
use JustSteveKing\DataObjects\Contracts\HydratorContract;
class Hydrate implements HydratorContract
{
public function __construct(
private readonly ObjectMapperUsingReflection $mapper = new ObjectMapperUsingReflection(),
) {}
public function fill(string $class, array $properties): DataObjectContract
{
return $this->mapper->hydrateObject(
className: $class,
payload: $properties,
);
}
}
PackageServiceProvider
的頂部:public array $bindings = [
HydratorContract::class => Hydrate::class,
];
src/Facades/Hydrator.php
下創建它declare(strict_types=1);
namespace JustSteveKing\DataObjects\Facades;
use Illuminate\Support\Facades\Facade;
use JustSteveKing\DataObjects\Contracts\DataObjectContract;
use JustSteveKing\DataObjects\Hydrator\Hydrate;
/**
* @method static DataObjectContract fill(string $class, array $properties)
*
* @see \JustSteveKing\DataObjects\Hydrator\Hydrate;
*/
final class Hydrator extends Facade
{
/**
* @return class-string
*/
protected static function getFacadeAccessor(): string
{
return Hydrate::class;
}
}
"extra": {
"laravel": {
"providers": [
"JustSteveKing\\DataObjects\\Providers\\PackageServiceProvider"
],
"aliases": [
"JustSteveKing\\DataObjects\\Facades\\Hydrator"
]
}
},
tests/Facades/HydratorTest.php
下創建一個新的測試文件,讓我們開始吧:declare(strict_types=1);
use JustSteveKing\DataObjects\Facades\Hydrator;
use JustSteveKing\DataObjects\Tests\Stubs\Test;
it('can create a data transfer object', function (string $string) {
expect(
Hydrator::fill(
class: Test::class,
properties: ['name' => $string],
),
)->toBeInstanceOf(Test::class)->toArray()->toEqual(['name' => $string]);
})->with('strings');
toArray
方法時,我們會測試該實例是否已創建以及它是否符合我們的預期。我們可以使用反射 API 來確保為最終測試按預期創建 DTO。it('creates our data transfer object as we would expect', function (string $string) {
$test = Hydrator::fill(
class: Test::class,
properties: ['name' => $string],
);
$reflection = new ReflectionClass(
objectOrClass: $test,
);
expect(
$reflection->getProperty(
name: 'name',
)->isReadOnly()
)->toBeTrue()->and(
$reflection->getProperty(
name: 'name',
)->isPrivate(),
)->toBeTrue()->and(
$reflection->getMethod(
name: 'toArray',
)->hasReturnType(),
)->toBeTrue();
})->with('strings');
composer require --dev laravel/pint
pint.json
以確保我們配置 pint 以運行我們想要運行的標準:{
"preset": "psr12"
}
./vendor/bin/pint
composer require --dev phpstan/phpstan
phpstan.neon
以了解正在使用的配置。parameters:
level: 9
paths:
- src
./vendor/bin/phpstan analyse
[OK] No errors
以上就是關于“怎么構建一個自己的Laravel包”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。