您好,登錄后才能下訂單哦!
這篇“怎么構(gòu)建一個(gè)自己的Laravel包”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“怎么構(gòu)建一個(gè)自己的Laravel包”文章吧。
使用您的包名稱(chēng)創(chuàng)建一個(gè)新目錄,然后在您選擇的代碼編輯器中打開(kāi)它,以便我們開(kāi)始設(shè)置。 我對(duì)任何新包做的第一件事是將其初始化為 git 存儲(chǔ)庫(kù),因此運(yùn)行以下 git 命令:
git init
現(xiàn)在我們有了一個(gè)可以使用的存儲(chǔ)庫(kù),我們將能夠?qū)?nèi)容提交到歷史版本,并允許在適當(dāng)?shù)臅r(shí)候?qū)ΠM(jìn)行版本控制。
創(chuàng)建一個(gè) PHP 包需要馬上做一件事:一個(gè) composer.json
文件,它會(huì)告訴 Packagist 這個(gè)包是什么以及它需要運(yùn)行什么。你可以使用命令行 Composer 工具或手動(dòng)創(chuàng)建 Composer 文件。我通常使用命令行 composer init
,因?yàn)樗且环N交互式的設(shè)置方式;但是,我將顯示我的 Composer 文件開(kāi)頭的輸出,以便你可以看到結(jié)果:
{ "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 } }
這是我的大多數(shù)包的基礎(chǔ)結(jié)構(gòu),無(wú)論是 Laravel 還是普通的 PHP 包,它以一種我已知并保持風(fēng)格一致的方式進(jìn)行設(shè)置。我們需要在包中添加一些支持文件才能開(kāi)始。首先,我們需要添加
.gitignore
文件,這樣我們就可以告訴版本控制我們不想提交哪些文件和目錄:
這是我們要忽略的文件的開(kāi)始。我正在使用 PHPStorm,它將添加一個(gè)名為 創(chuàng)建版本時(shí),我們會(huì)告訴源代碼控制提供者我們想要忽略哪些文件以及如何處理差異。最后,我們的最后一個(gè)支持文件將是 現(xiàn)在我們有了版本控制的支持文件和編輯器,我們可以開(kāi)始考慮我們的包在依賴(lài)關(guān)系方面需要什么。我們的包將依賴(lài)哪些依賴(lài)項(xiàng),以及我們使用哪些版本?讓我們開(kāi)始吧。/vendor/
/.idea
composer.lock
.idea
的元目錄,其中包含我的 IDE 理解我的項(xiàng)目所需的所有信息——我不想提交版本控制。接下來(lái),我們需要添加一些 git 的屬性配置,以便版本控制知道如何處理我們的存儲(chǔ)庫(kù)。這稱(chēng)為.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
,該文件告訴我們的代碼編輯器如何處理我們正在編寫(xiě)的文件: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
當(dāng)我們正在構(gòu)建一個(gè) Laravel 包時(shí),我們首先需要的是 Laravel 支持包,所以使用以下 composer 命令安裝它:
composer require illuminate/support
現(xiàn)在可以著手做一些事情,來(lái)看一下包需要的代碼的第一個(gè)重要部分:服務(wù)提供者。服務(wù)提供者是所有 Laravel 包的關(guān)鍵部分,因?yàn)樗嬖V Laravel 如何加載包以及可用的包。首先,我們想讓 Laravel 知道我們有一個(gè)安裝后可以使用的控制臺(tái)命令。我已經(jīng)調(diào)用了我的服務(wù)提供商
PackageServiceProvider
,因?yàn)槲蚁胂罅τ邢蓿也粫?huì)起名。如果您愿意,請(qǐng)隨意更改您自己的命名。我在 src/Providers
下添加了我的服務(wù)提供商,因?yàn)樗煜?Laravel 應(yīng)用程序。
我通常將我知道不希望擴(kuò)展的類(lèi)作為最終類(lèi),因?yàn)檫@樣做會(huì)改變我希望包的操作方式。你不需要這樣做。這是你需要為自己做出的判斷。所以我們現(xiàn)在注冊(cè)了一個(gè)命令。我們應(yīng)該考慮創(chuàng)建它。從命名中可以看出,它是一個(gè)將為我們生成其他類(lèi)的命令——與典型的工匠命令略有不同。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,
],
);
}
}
}
我創(chuàng)建了一個(gè)名為 DataTransferObjectMakeCommand
的類(lèi),它非常冗長(zhǎng),但解釋了它在 src/Console/Commands
內(nèi)部的作用。如你所見(jiàn),在創(chuàng)建這些類(lèi)時(shí),我嘗試反映 Laravel 開(kāi)發(fā)人員熟悉的目錄結(jié)構(gòu)。這樣做會(huì)使使用包變得更加容易。讓我們看一下這個(gè)命令的代碼:
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"; } }
讓我們通過(guò)這個(gè)命令來(lái)了解我們正在創(chuàng)建什么。我們的命令想要擴(kuò)展
GeneratorCommand
,因?yàn)槲覀兿胍梢粋€(gè)新文件。理解這一點(diǎn)很有用,因?yàn)閹缀鯖](méi)有關(guān)于如何做到這一點(diǎn)的文檔。對(duì)于這個(gè)命令,我們唯一需要的是一個(gè)名為 getStub
的方法--該命令需要知道如何加載存根文件的位置以幫助生成文件。我在包的根目錄中創(chuàng)建了一個(gè)名為 stubs
的目錄,這是 Laravel 應(yīng)用程序熟悉的地方。您將在這里看到我正在檢查已安裝的 PHP 版本,以查看我們是否使用 PHP 8.2,如果是 - 我們希望加載正確的存根版本以利用只讀類(lèi)。現(xiàn)在發(fā)生這種情況的可能性非常低 - 但是,我們離我們并不遙遠(yuǎn)。這種方法有助于為特定的 PHP 版本生成文件,因此您可以確保支持您希望支持的每個(gè)版本。
最后,我已經(jīng)為我的 DTO 設(shè)置了默認(rèn)命名空間,所以我知道我希望它們放在哪里。畢竟我不想過(guò)度填充根命名空間。
先來(lái)快速了解一下這些存根文件,默認(rèn)的命名空間為 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 將實(shí)施一個(gè)契約來(lái)保證一致性——我喜歡盡可能多地使用這些類(lèi)。此外,我們的 DTO 類(lèi)是 final 類(lèi)。我們可能不想擴(kuò)展這個(gè)類(lèi),所以默認(rèn)情況下將其設(shè)為 final 是一種明智的做法。現(xiàn)在讓我們看一下 PHP 8.2 版本:
這里唯一的區(qū)別是我們將 DTO 類(lèi)設(shè)為只讀以利用該語(yǔ)言的新特性。<?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 [];
}
}
我們?nèi)绾螠y(cè)試這個(gè)?首先,我們要安裝一個(gè)測(cè)試包,以確保我們可以編寫(xiě)運(yùn)行此命令的測(cè)試 - 我將為此使用 pestPHP,使用 PHPUnit 將可以以非常相似的方式工作。
composer require pestphp/pest --dev --with-all-dependencies
此命令將要求您允許 Pest 使用 Composer 插件,因此如果您需要 Pest 插件進(jìn)行測(cè)試(例如并行測(cè)試),請(qǐng)確保您對(duì)此表示同意。接下來(lái),我們需要一個(gè)允許我們?cè)跍y(cè)試中使用 Laravel 的包,以確保我們的包有效地工作。這個(gè)包叫做 Testbench,是我在構(gòu)建 Laravel 包時(shí)使用的。
在我們的包中初始化測(cè)試套件的最簡(jiǎn)單方法是使用 pesPHP 為我們初始化它。運(yùn)行以下控制臺(tái)命令: 這將生成 composer require --dev orchestra/testbench
./vendor/bin/pest --init
phpunit.xml
文件和一個(gè) tests/Pest.php
文件,用于控制和擴(kuò)展 pest。首先,我喜歡對(duì) Pest 要使用的 PHPUnit 配置文件進(jìn)行一些更改。我喜歡添加以下選項(xiàng)以使我的測(cè)試更容易:
stopOnFailure
我設(shè)置為 truecacheResults
我設(shè)置為 false
我這樣做是因?yàn)槿绻麥y(cè)試失敗,我想立即知道。越早的返回和失敗有助于我們構(gòu)建更有信心的東西。緩存結(jié)果可以加速你的包的測(cè)試。但是,我喜歡確保每次都從頭開(kāi)始運(yùn)行我的測(cè)試套件,以確保它按我的預(yù)期工作。
現(xiàn)在讓我們將注意力集中在一個(gè)默認(rèn)測(cè)試用例上,我們需要我們的包測(cè)試來(lái)運(yùn)行它。在 tests/PackageTestCase.php
下創(chuàng)建一個(gè)新文件,這樣我們就可以更輕松地控制我們的測(cè)試。
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
擴(kuò)展了測(cè)試平臺(tái)TestCase
,因此我們可以從包中借用行為來(lái)構(gòu)建我們的測(cè)試套件。然后我們注冊(cè)我們的包服務(wù)提供者,以確保我們的包被加載到測(cè)試應(yīng)用程序中。
現(xiàn)在讓我們看看如何測(cè)試它。在我們編寫(xiě)測(cè)試之前,我們要確保我們測(cè)試的內(nèi)容涵蓋了包的當(dāng)前行為。到目前為止,我們的測(cè)試所做的只是提供一個(gè)命令,可以運(yùn)行該命令來(lái)創(chuàng)建一個(gè)新文件。我們的測(cè)試目錄結(jié)構(gòu)將反映我們的包結(jié)構(gòu),所以在 tests/Console/Commands/DataTransferObjectMakeCommandTest.php
下創(chuàng)建我們的第一個(gè)測(cè)試文件,然后開(kāi)始我們的第一個(gè)測(cè)試。
在我們編寫(xiě)第一個(gè)測(cè)試之前,我們需要編輯 tests/Pest.php
文件以確保我們的測(cè)試套件正確使用我們的 PackageTestCase
。
declare(strict_types=1); use JustSteveKing\DataObjects\Tests\PackageTestCase; uses(PackageTestCase::class)->in(__DIR__);
首先,要確保我們的命令可以運(yùn)行并且運(yùn)行成功。所以添加以下測(cè)試:
我們正在測(cè)試當(dāng)我們調(diào)用這個(gè)命令時(shí),運(yùn)行沒(méi)有錯(cuò)誤。如果您問(wèn)我,這是最關(guān)鍵的測(cè)試之一,如果它出錯(cuò),則意味著出現(xiàn)問(wèn)題。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();
});
既然我們知道我們的測(cè)試可以運(yùn)行,我們還想確保創(chuàng)建了類(lèi)。所以讓我們接下來(lái)編寫(xiě)這個(gè)測(cè)試:
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 來(lái)運(yùn)行一些選項(xiàng),有點(diǎn)像 PHPUnit Data Provider。我們遍歷每個(gè)選項(xiàng)并調(diào)用我們的命令,斷言文件存在。我們現(xiàn)在知道可以將名稱(chēng)傳遞給我們的 artisan 命令并創(chuàng)建一個(gè) DTO 供我們?cè)趹?yīng)用程序中使用。
最后,我們想為我們的包構(gòu)建一個(gè) facade,以允許我們的 DTO 輕松水合。擁有 DTO 通常只是成功的一半,是的,我們可以向 DTO 本身添加一個(gè)方法來(lái)靜態(tài)調(diào)用 - 但我們可以大大簡(jiǎn)化這個(gè)過(guò)程。我們將通過(guò) Frank de Jonge 在他的 Eventsauce 包 中使用一個(gè)非常有用的包來(lái)促進(jìn)這一點(diǎn),稱(chēng)為「對(duì)象保濕劑」。請(qǐng)運(yùn)行以下 composer 命令安裝它:
composer require eventsauce/object-hydrator
是時(shí)候圍繞這個(gè)包構(gòu)建一個(gè)包裝器,以便我們可以很好地使用它,所以讓我們?cè)?
src/Hydrator/Hydrate.php
下創(chuàng)建一個(gè)新類(lèi),如果需要,我們還將創(chuàng)建一個(gè)契約在任何時(shí)候交換實(shí)現(xiàn)。這將是src/Contracts/HydratorContract.php
。讓我們從契約開(kāi)始,了解我們想要它做什么。
我們所需要的只是一種水合對(duì)象的方法,因此我們使用對(duì)象的類(lèi)名和一組屬性來(lái)返回一個(gè)數(shù)據(jù)對(duì)象。現(xiàn)在讓我們看一下實(shí)現(xiàn): 我們有一個(gè)對(duì)象映射器傳遞給構(gòu)造函數(shù)或在構(gòu)造函數(shù)中創(chuàng)建 - 然后我們?cè)谔畛浞椒ㄖ惺褂盟?。然后填充方法使用映射器?lái)水合對(duì)象。它使用簡(jiǎn)單干凈,如果我們將來(lái)選擇使用不同的保濕器,可以輕松復(fù)制。但是,使用這種方式,我們希望將水化器綁定到容器中,以允許我們使用依賴(lài)注入來(lái)解決它。將以下內(nèi)容添加到 現(xiàn)在我們有了 hydrator,我們需要?jiǎng)?chuàng)建一個(gè) facade,以便我們可以在我們的應(yīng)用程序中很好地調(diào)用它。現(xiàn)在讓我們?cè)? 所以我們的外觀(guān)當(dāng)前返回的是 Hydrator 的事件實(shí)現(xiàn)-這意味著我們無(wú)法從容器中解決這個(gè)問(wèn)題,所以如果我們切換實(shí)現(xiàn),我們將需要更改 facade。不過(guò),就目前而言,這還不是什么大事。接下來(lái),我們需要將此別名添加到我們的文件中,以便 Laravel 在我們安裝軟件包時(shí)知道它。 現(xiàn)在我們已經(jīng)注冊(cè)了 Facade,我們需要測(cè)試它是否按預(yù)期工作。讓我們來(lái)看看如何測(cè)試它。在 我們創(chuàng)建了一個(gè)名為 strings 的新數(shù)據(jù)集,它返回一個(gè)隨機(jī)字符串?dāng)?shù)組供我們使用。我們將它傳遞給我們的測(cè)試并嘗試在我們的 facade 上調(diào)用填充方法。傳入一個(gè)測(cè)試類(lèi),我們可以創(chuàng)建一組屬性來(lái)進(jìn)行水合。然后,當(dāng)我們?cè)?DTO 上調(diào)用 我們現(xiàn)在可以確定我們的包按預(yù)期工作。我們需要做的最后一件事是關(guān)注代碼的質(zhì)量。在我的大多數(shù)包中,我喜歡確保編碼風(fēng)格和靜態(tài)分析都在運(yùn)行,這樣我就有了一個(gè)值得信賴(lài)的可靠包。讓我們從代碼樣式開(kāi)始。為此,我們將安裝一個(gè)名為 Laravel Pint 的相對(duì)較新的軟件包: 我喜歡使用 PSR-12 作為我的代碼風(fēng)格,所以讓我們?cè)诎母夸浿袆?chuàng)建一個(gè) 現(xiàn)在運(yùn)行 pint 命令來(lái)修復(fù)任何不符合 PSR-12 的代碼樣式問(wèn)題: 最后,我們可以安裝 PHPStan,這樣我們就可以靜態(tài)分析我們的代碼,以確保我們盡可能?chē)?yán)格并與我們的類(lèi)型保持一致: 要配置 PHPStan,我們需要在包的根目錄中創(chuàng)建一個(gè) 最后,我們可以運(yùn)行 PHPStan 來(lái)分析我們的代碼 如果一切順利,我們現(xiàn)在應(yīng)該會(huì)看到 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
下創(chuàng)建它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
下創(chuàng)建一個(gè)新的測(cè)試文件,讓我們開(kāi)始吧: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
方法時(shí),我們會(huì)測(cè)試該實(shí)例是否已創(chuàng)建以及它是否符合我們的預(yù)期。我們可以使用反射 API 來(lái)確保為最終測(cè)試按預(yù)期創(chuàng)建 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 以運(yùn)行我們想要運(yùn)行的標(biāo)準(zhǔn):{
"preset": "psr12"
}
./vendor/bin/pint
composer require --dev phpstan/phpstan
phpstan.neon
以了解正在使用的配置。parameters:
level: 9
paths:
- src
./vendor/bin/phpstan analyse
[OK] No errors
以上就是關(guān)于“怎么構(gòu)建一個(gè)自己的Laravel包”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。