Middle: питання на співбесіді з теми «Розробка пакетів»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Пакет має працювати одразу після встановлення з розумними значеннями за замовчуванням, але дозволяти змінити їх застосунку.
mergeConfigFrom (у register) - злиття конфігурації пакета з конфігурацією застосунку:
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/invoices.php', 'invoices');
}
Тепер config('invoices.currency') працює, навіть якщо застосунок нічого не публікував.
publishes (у boot) - дозволяє скопіювати файл у застосунок для редагування:
public function boot(): void
{
$this->publishes([
__DIR__.'/../config/invoices.php' => config_path('invoices.php'),
], 'invoices-config');
}
php artisan vendor:publish --tag=invoices-config
Як працює злиття - і головна пастка:
// всередині mergeConfigFrom
array_merge(require $packageConfig, config('invoices', []));
- значення застосунку перемагають значення пакета;
- злиття неглибоке - лише перший рівень ключів. Якщо пакет має
'pdf' => ['size' => 'A4', 'orientation' => 'portrait'], а застосунок у своєму файлі вказав'pdf' => ['size' => 'Letter'], ключorientationзникне повністю; - для рекурсивного злиття є
replaceConfigRecursivelyFrom(), але воно має свої наслідки для масивів-списків.
Звідси правило проєктування конфігурації пакета: тримати її пласкою, а вкладені групи - мінімальними, і документувати, що опублікований файл замінює групу цілком.
config:cache: коли конфігурацію закешовано, mergeConfigFrom нічого не робить - значення вже в кеші. Тому змінюючи конфігурацію пакета в розробці, не забувайте config:clear.
Теги публікації:
- конвенція -
<пакет>-config,<пакет>-migrations,<пакет>-views; vendor:publish --provider="Acme\Invoices\InvoicesServiceProvider"публікує все від провайдера;- міграції краще публікувати через
publishesMigrations()- Laravel оновить мітку часу в імені файлу, щоб міграція виконалася після наявних; - не змушуйте публікувати конфігурацію для базової роботи: якщо без
vendor:publishпакет падає - це погана інтеграція.
Значення з оточення: у конфігурації пакета можна використовувати env('INVOICES_CURRENCY', 'UAH'), а в коді пакета - лише config(), бо після config:cache виклики env() поза конфігураційними файлами повертають null.
Усе підключається в методі boot сервіс-провайдера пакета:
public function boot(): void
{
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
$this->loadViewsFrom(__DIR__.'/../resources/views', 'invoices');
$this->loadTranslationsFrom(__DIR__.'/../lang', 'invoices');
$this->loadJsonTranslationsFrom(__DIR__.'/../lang');
if ($this->app->runningInConsole()) {
$this->commands([InstallCommand::class]);
}
}
Використання з простором імен пакета:
return view('invoices::pdf', ['invoice' => $invoice]);
__('invoices::messages.paid');
<x-invoices::status-badge :status="$invoice->status" />
Як застосунок перевизначає представлення без форку пакета: loadViewsFrom реєструє два шляхи - спершу resources/views/vendor/invoices застосунку, потім каталог пакета. Достатньо опублікувати чи створити файл з тим самим іменем:
$this->publishes([
__DIR__.'/../resources/views' => resource_path('views/vendor/invoices'),
], 'invoices-views');
Переклади працюють так само: файли в lang/vendor/invoices застосунку мають пріоритет.
Особливості кожного ресурсу:
| Ресурс | На що зважати |
|---|---|
| маршрути | loadRoutesFrom нічого не робить, якщо маршрути закешовано; дайте можливість вимкнути маршрути чи змінити префікс і middleware через конфігурацію |
| міграції | loadMigrationsFrom запускає міграції пакета разом з міграціями застосунку - зручно, але застосунок не може їх змінити. Альтернатива - лише публікація |
| представлення | ім'я простору - унікальне, щоб не зіткнутися з іншими пакетами |
| компоненти | Blade::componentNamespace('Acme\\Invoices\\Views', 'invoices') чи анонімні компоненти з каталогу представлень |
| команди | реєструвати лише в консолі - runningInConsole() |
Маршрути пакета - найбільша зона конфліктів: пакет, що безумовно реєструє /login чи /admin, може перекрити маршрути застосунку. Гарна практика:
if (config('invoices.routes.enabled', true)) {
Route::prefix(config('invoices.routes.prefix', 'invoices'))
->middleware(config('invoices.routes.middleware', ['web', 'auth']))
->group(__DIR__.'/../routes/web.php');
}
Міграції з моделями користувача: таблиці, що посилаються на users, мають враховувати, що модель користувача й тип ключа (int, UUID) у застосунку можуть бути іншими - краще брати їх із конфігурації.
Пакет не має власного застосунку - немає bootstrap/app.php, контейнера, конфігурації. Orchestra Testbench створює мінімальний Laravel-застосунок для тестів пакета.
composer require --dev orchestra/testbench pestphp/pest
Версії Testbench відповідають версіям Laravel:
| Testbench | Laravel |
|---|---|
| 9.x | 11.x |
| 10.x | 12.x |
| 11.x | 13.x |
Базовий тест-кейс:
namespace Acme\Invoices\Tests;
use Acme\Invoices\InvoicesServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;
abstract class TestCase extends Orchestra
{
protected function getPackageProviders($app): array
{
return [InvoicesServiceProvider::class];
}
protected function defineEnvironment($app): void
{
$app['config']->set('database.default', 'testing');
$app['config']->set('invoices.currency', 'UAH');
}
protected function defineDatabaseMigrations(): void
{
$this->loadLaravelMigrations();
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
}
// tests/Pest.php
uses(Acme\Invoices\Tests\TestCase::class)->in(__DIR__);
Тепер у тестах працює все, що й у застосунку: HTTP-тести маршрутів пакета, фабрики, Mail::fake(), artisan():
it('generates an invoice pdf', function () {
$this->get('/invoices/1/pdf')->assertOk()->assertHeader('content-type', 'application/pdf');
});
it('publishes the config', function () {
$this->artisan('vendor:publish', ['--tag' => 'invoices-config'])->assertSuccessful();
});
Workbench - для ручної перевірки: vendor/bin/testbench workbench:install створює каталог workbench/ з маршрутами й моделями, а vendor/bin/testbench serve запускає вебсервер з підключеним пакетом.
Що обов'язково протестувати в пакеті:
- пакет працює без опублікованої конфігурації;
- реєстрація провайдера не падає за відсутності необов'язкових залежностей;
- міграції застосовуються й відкочуються;
- поведінку на всіх підтримуваних версіях Laravel і PHP - матриця в CI (
laravel: [12.*, 13.*]з відповідними версіями Testbench); --prefer-lowestу CI - що пакет справді працює з мінімальними версіями, вказаними вcomposer.json.