Как создать PHP пакет

Источник: «How to Build Your First PHP Package»
Если хотите создать PHP пакет с нуля и поделиться им с другими PHP разработчиками, Composer — это менеджер зависимостей, упрощающий этот процесс! Благодаря Composer PHP имеет одну из лучших экосистем пакетов. Погрузимся в этот процесс и пройдёмся по шагам создания PHP пакета.

Подготовка к созданию PHP пакета

Основная цель статьи — помочь новичкам в PHP (или новичкам в написании PHP пакетов), желающим научиться создавать PHP пакеты с нуля.

В процессе установки нового PHP пакета необходимо выполнить несколько действий:

Хотя можно было бы начать с создания пустого проекта на GitHub и его локального клонирования, вместо этого создадим локально новую папку, инициализируем проект, а затем разместим исходный код на GitHub:

mkdir example-package
cd ./example-package
git init
echo "/vendor/" >> .gitignore
composer init
git add .
git commit -m"First Commit"
# позже вы сможете добавить удалённый репозиторий и выложить на него исходный код

Команда composer init проведёт вас через интерактивную настройку проекта, задаст такие параметры, как имя PHP пакета, авторы и лицензия, а также выполнит поиск зависимостей пакета. Не стесняйтесь заполнять эти параметры, но для краткости приведём отправную точку:

{
"name": "laravelnews/feeds",
"description": "Get articles from Laravel-News.com",
"type": "library",
"require": {}
}

У нас есть базовая конфигурация для PHP пакета, но она мало что даёт. Некоторые пакеты не требуют никаких зависимостей, если в пакете будет использоваться только основной язык PHP. В любом случае необходимо настроить автозагрузку, чтобы пользователи пакета могли загружать функции и классы в свои проекты.

Когда будете готовы подключить локальную проверку к системе контроля версий, например GitHub, следуйте инструкциям по добавлению удалённого репозитория. Это может выглядеть так:

git remote add origin git@github.com:laravelnews/example-package.git

Настройка автозагрузки

После создания базовой структуры composer.json можно переходить к созданию исходного кода. Необходимо решить, где будет храниться исходный код в проекте. Папка может называться как угодно, но типичным стандартом является src/ или lib/. Composer неважно, какой путь (пути) использовать, однако необходимо указать Composer, откуда он сможет автоматически загружать файлы с помощью PSR-4. Воспользуемся папкой src и создадим класс для примера PHP пакета:

mkdir src/
touch src/Api.php

Затем откройте файл composer.json и настройте автозагрузку используя ключ autoload:

{
"name": "laravelnews/feeds",
"description": "Get articles from Laravel-News.com",
"type": "library",
"require": {},
"autoload": {
"psr-4": {
"LaravelNews\\Feed\\": "src/"
}
}
}

Свойства ключа autoload.psr-4 сопоставляют пространства имён PHP с папками. При создании файлов в папке src они будут сопоставлены с пространством имён LaravelNews\Feed. Для этого примера будет создан файл Api.php, запрашивающий и возвращающий JSON-ленту Laravel News. Если вы следите за процессом, добавьте следующий код в src/Api.php:

<?php

namespace LaravelNews\Feed;

class Api
{
public function json(): array
{
$json = file_get_contents('https://laravel-news.com/feed/json');

return json_decode($json, true);
}
}

Как попробовать новый класс прямо сейчас?

Есть несколько способов, например, потребовать этот PHP пакет в другом проекте с помощью локальных зависимостей Composer или даже выложить код на GitHub и обновить пакет composer update используя dev-main. Однако можно просто создать файл index.php в корне проекта, чтобы опробовать его:

use LaravelNews\Feed\Api;

require __DIR__.'/vendor/autoload.php';

$response = (new Api)->json();

echo "The Laravel-News.com feed has returned ".count($response['items']['items'])." items.\n";
// ...

Для этого понадобился автозагрузчик Composer, который знает, как загрузить файлы для PHP пакета. Чтобы Composer понял, как найти файлы, необходимо запустить composer install:

$ composer install
# или
$ composer dump-autoload
$ php index.php
The Laravel-News.com feed has returned 20 items.

Также можно выполнить команду dump-autoload, чтобы обновить автозагрузку Composer после добавления пространства имён в composer.json.

Тесты PHP пакета и зависимости разработки

Рекомендую писать тесты для любого проекта, над которым работаете, и мне нравится создавать тесты как можно раньше. При создании PHP пакета наиболее распространённым фреймворком для тестирования является PHPUnit. В последнее время мне больше всего нравится Pest PHP, и думаю, вам понравится, как легко его настроить!

Пакеты Composer имеют два набора требований: раздел require включает пакеты, необходимые для запуска пакета, а require-dev — пакеты, необходимые для тестирования. Пока что нет пакетов require, и возможно, что так и останется, если не нужны другие PHP пакеты.

Сомневаюсь, что вам захочется писать собственный фреймворк для тестирования с нуля, поэтому устанавливаем первую зависимость для разработки. Также нам не всегда хочется делать запросы к живой конечной точке JSON, поэтому установим mocking-библиотеку (Mockery) для имитации HTTP-вызовов:

composer require pestphp/pest --dev --with-all-dependencies
composer require --dev mockery/mockery

После установки Pest и Mockery можно инициализировать Pest с помощью флага --init. После создания файлов можно запустить pest для тестирования кода PHP пакета:

vendor/bin/pest --init
# ...
vendor/bin/pest
PASS Tests\Feature\ExampleTest
✓ example

PASS Tests\Unit\ExampleTest
✓ example

Tests: 2 passed (2 assertions)
Duration: 0.06s

Вы можете организовать тесты своего PHP пакета любым удобным для себя способом, и я рекомендую ознакомиться с документацией Pest для получения подробной информации о настройке Pest.

Далее создадим простой класс, который можно использовать для демонстрации тестирования PHP пакета. Этот класс будет получать последние статьи из JSON-ленты Laravel News и возвращать последнюю статью.

Назовём этот вымышленный класс NewsChecker и добавим его в файл src/NewsChecker.php со следующим содержимым:

<?php

namespace LaravelNews\Feed;

class NewsChecker
{

public function __construct(
private Api $api
) {}

public function latestArticle(): array
{
$response = $this->api->json();
$items = $response['items']['items'] ?? [];

if (empty($items)) {
throw new \Exception("Unable to retrieve the latest article from Laravel-News.com");
}

usort($items, function($a, $b) {
return strtotime($b['date_published']) - strtotime($a['date_published']);
});

return $items[0];
}
}

Обратите внимание, что он берет в качестве зависимости класс Api, который будем имитировать в тестах.

Далее создадим этот файл в tests/Feature/NewsCheckerTest.php и добавим следующие тесты для проверки метода latestArticle():

use LaravelNews\Feed\Api;
use LaravelNews\Feed\NewsChecker;

it('Returns the latest article on Laravel-News.com', function () {
$items = [
[
'id' => 3648,
'title' => "Laravel SEO made easy with the Honeystone package",
'date_published' => "2024-08-20T13:00:00+00:00",
],
[
'id' => 3650,
'title' => "LCS #5 - Patricio: Mingle JS, PHP WASM, VoxPop",
'date_published' => "2024-08-23T13:00:00+00:00",
],
[
'id' => 3647,
'title' => "Laravel Model Tips",
'date_published' => "2024-08-22T13:00:00+00:00",
],
];

$api = Mockery::mock(Api::class);
$api->shouldReceive('json')->once()->andReturn([
'title' => 'Laravel News Feed',
'feed_url' => 'https://laravel-news.com/feed/json',
'items' => [
'items' => $items,
],
]);

$checker = new NewsChecker($api);
$article = $checker->latestArticle();

expect($article['title'])->toBe("LCS #5 - Patricio: Mingle JS, PHP WASM, VoxPop");
});

it('Throws an exception if no items are returned from the feed', function () {
$api = Mockery::mock(Api::class);
$api->shouldReceive('json')->once()->andReturn([
'title' => 'Laravel News Feed',
'feed_url' => 'https://laravel-news.com/feed/json',
]);

$checker = new NewsChecker($api);

expect(fn() => $checker->latestArticle())
->toThrow(new Exception('Unable to retrieve the latest article from Laravel-News.com'));
});

Можно запустить эти тесты и убедиться, что код работает, запустив vendor/bin/pest. Не стесняйтесь удалять примеры тестов, созданные после выполнения pest --init.

Мы проделали довольно большую работу: инициализировали Git-репозиторий, настроили PHP пакет с помощью composer.json, добавили исходный код и тесты, а также запустили их с помощью Pest. Теперь вы готовы опубликовать свой пакет на Packagist!

Узнать больше

Я рекомендую подписаться и ознакомиться с документацией на Packagist.org, где вы будете публиковать новые версии PHP пакета. Процесс обновления версий PHP пакета на Packagist может быть автоматизирован, то есть, когда вы отмечаете тегом новые версии пакета, они автоматически появляются на Packagist.org.

Мы рассмотрели создание PHP пакета с нуля, но если вы используете GitHub, то создание шаблона репозитория для вашей организации или личных проектов может ускорить процесс ещё сильнее! В сообществе есть несколько скелетов пакетов, которые можно использовать в качестве отправной точки для создания следующего пакета Composer:

Дополнительные материалы

Предыдущая Статья

Десять редко используемых правил валидации Laravel

Следующая Статья

Советы по Моделям Laravel