Кращі практики для повторно використовуваних пакетів

Дата оновлення перекладу 2024-04-30

Кращі практики для повторно використовуваних пакетів

Ця стаття повністю розкриває те, як структурувати ваші повторно використовувані пакети таким чином, щоб їх було легко конфігурувати та розширювати. Повторно використовувані пакети - це ті пакети, які мають розповсюджуватися приватно між багатьма проектами компанії, або публічно, щоб будь-який проект Symfony міг їх встановити.

Імʼя пакета

Пакет також є PHP простором імен. Простір імені має слідувати стандарту сумісності або PSR-4 для PHP просторів імен та імен класів: він має починатися з сегменту постачальника, потім йде нуль або інші сегменти категорій, і закінчується він коротким іменем простру імен, яке має закінчуватися суфіксом Bundle.

Простір імен стає пакетом, як тільки ви додаєте до нього "клас пакета" (який є класом, що розширює Bundle). Імʼя класу пакета має слідувати цим простим правилам:

  • Використовувати лише алфавітно-цифрові символи та нижні підкреслення;
  • Використовувати імʼя StudlyCaps (тобто. camelCase з першою великою літерою);
  • Використовувати описове та коротке імʼя (не більше двох слів);
  • На початку імені додавати конкатенацію постачальника (і, за бажанням,
    простору імен категорії);
  • Додавати до імені суфікс Bundle.

Ось декілька валідних просторів імен пакетів та імен класів:

??????? ???? ???? ????? ??????
Acme\Bundle\BlogBundle AcmeBlogBundle
Acme\BlogBundle AcmeBlogBundle

За угодою, метод getName() класу пакета має повертати імʼя класу.

Note

Якщо ви публічно ділитеся вашим пакетом, то ви маєте використовувати імʼя класу пакета в якості імені сховища (наприклаж, AcmeBlogBundle, а не BlogBundle).

Note

Базові пакети Symfony не додають на початку класу пакета Symfony і завжди додають підпростори імен Bundle; наприклад: FrameworkBundle.

Кожний пакет має додаткове імʼя, яке є короткою версією простору імен пакета в нижньому регістрі з використанням нижніх підкреслень (acme_blog для
AcmeBlogBundle). Це додаткове імʼя використовується для підсилення унікальності в проекті та для визначення опцій конфігурації пакета (див. нижче деякі приклади використання).

Структура каталогу

Базова структура каталогу AcmeBlogBundle має виглядати так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<your-bundle>/
├── assets/
├── config/
├── docs/
│   └─ index.md
├── public/
├── src/
│   ├── Controller/
│   ├── DependencyInjection/
│   └── AcmeBlogBundle.php
├── templates/
├── tests/
├── translations/
├── LICENSE
└── README.md

Ця структура каталогу вимагає конфігурації шляху пакета до його кореневого каталогу наступним чином:

1
2
3
4
5
6
7
class AcmeBlogBundle extends Bundle
{
    public function getPath(): string
    {
        return \dirname(__DIR__);
    }
}

Наступні файли є обовʼязковими так як вони гарантують узгодження структури, на яку можуть покластися автоматизовані інструменти:

  • src/AcmeBlogBundle.php: Це клас, що трансформує простий каталог у пакет Symfony (змініть його на імʼя вашого пакета);
  • README.md: Цей файл містить базовий опис пакета та зазвичай демонструє базові приклади та посилання на повну документацію (може використовувати будь-які формати розмітки, які підтримуються GitHub, наприклад, README.rst);
  • LICENSE: Повнний зміст ліцензії, що використовується кодом. Більшість сторонніх пакетів публікується під ліцензією MIT, але ви можете обрати будь-яку ліцензію;
  • docs/index.rst: Кореневий файл документації пакета.

Глибина підкаталогів має бути мінімальною для більшості використовуваних класів та файлів. Два рівня - це максимум.

Каталог пакета доступний лише для читання. Якщо вам потрібно написати тимчасові файли, зберігайте їх у каталозі cache/ або log/ хостингу додатку. Інструменти можуть генерувати файли у структурі каталогу пакета, ала лише якщо згенеровані файли будут частиною сховища.

Наступні класи та файли мають специфічні місцезнаходження (деякі обовʼязкові, а інші є просто угодою, якій слідують більшість розробників):

??? ???????
??????? src/Command/
?????????? src/Controller/
?????????? ??????-?????????? src/DependencyInjection/
???????? Doctrine ORM (?? ?????????????? ????????) src/Entity/
????????? Doctrine ODM (?? ?????????????? ????????) src/Document/
??????? ?????. src/EventListener/
???????????? (????????, ??????? ?? ??.) config/
???-??????? (CSS, JS, ??????????) public/
??????? ???-???????? (.scss, .ts, Stimulus) assets/
????? ?????????? translations/
????????? (?? ?????????????? ????????) config/validation/
???????????? (?? ?????????????? ????????) config/serialization/
??????? templates/
???????? ?? ????????????? ????? tests/

Класи

Структура каталогу пакета використовується в якості ієрархії простору імен. Наприклад, контролер ContentController, який зберігається в src/Controller/ContentController.php матиме повне імʼя класу Acme\BlogBundle\Controller\ContentController.

Всі класи та файли мають слідувати стандартам розробки кода Symfony.

Деякі класи мають розглядатися як фасади і мають бути максимально короткими, наприклад, Команди, Помічники, Слухачі та Контролери.

Класи, які звʼязуються з диспетчером подій, повинні мати суфікс Listener.

Класи виключень повиння зберігатися у підпросторі імен Exception.

Постачальники

Пакет також не має містити в собі сторонні PHP-бібліотеки. Замість цього, він має покладатися на стандартне автозавантаження Symfony.

Пакет не повинен включати в себе сторонні бібліотеки, написані на JavaScript, CSS або будь якими-іншими мовами.

Сутності/документи Doctrine

Якщо пакет містить сутності Doctrine ORM та/або документи ODM, рекомендовано визначити їх відображення з використанням файлів XML, збережених у config/doctrine/. Це дозволяє перевизначати це відображення, використовуючи стандартний механізм Symfony для перевизначення частин пакетів. Це неможливо при використанні атрибутів для визначення відображення.

Тести

Пакет має постачатися з комплектом тестів, що написані з PHPUnit та зберігаютьсся у каталозі tests/. Тести повинні слідувати наступним принципам:

  • Комплект тестів повинен виконуватися простою командою phpunit, запущеною з пробного додатку;
  • Функціональні тести мають використовуватися лише для тестування виведення відповіді та деякої профільної інформації, якщо вона у вас є;
  • Тести повинні охоплювати як мінімум 95% базового кода.

Note

Комплект тестів не повинен містити скрипти AllTests.php, але повинен покладатися на існування файлу phpunit.xml.dist.

Безперервна інтеграція

Безперервне тестування кода пакета, включно з усіма його фіксаціями та запитами на включення, це гарна практика під назвою "Безперервна інтеграція". Існує декілька сервісів, які надають цю функцію безкоштовно для проектів з відкритим початковим кодом, на кшталт GitHub Actions та Travis CI.

Пакет має щонайменше тестувати:

  • Нижню межу їх залежностей (виконавши composer update --prefer-lowest);
  • Підтримувані версії PHP;
  • Всі підтримувані старші версії Symfony (наприклад, як 4.x, так і 5.x, якщо підтримка оголошена для обох).

Отже, пакет, який підтримує PHP 7.3, 7.4 та 8.0, а також Symfony 4.4 і 5.x, повинен мати принаймні таку тестову матрицю:

?????? PHP ?????? Symfony ???????? Composer
7.3 4.* --prefer-lowest
7.4 5.*  
8.0 5.*  

Tip

Тести мають бути виконані зі змінною середовища SYMFONY_DEPRECATIONS_HELPER, встановленою як max[direct]=0. Це гарантує, що жодна частина коду в пакеті не використовує застарілі функції напряму.

Найнижчі тести залежностей можуть бути виконані з цією змінною, встановленою як disabled=1.

Вимога конкретної версії Symfony

Ви можете використати спеціальну зміннну середовища SYMFONY_REQUIRE разом з Symfony Flex, щоб встановити конкретну версію Symfony:

1
2
3
4
5
6
7
8
9
10
11
12
# це вимагає Symfony 5.x для всіх пакетів Symfony
export SYMFONY_REQUIRE=5.*
# як варіант, ви можете виконати цю команду, щоб оновити конфігурацію composer.json
# composer config extra.symfony.require "5.*"

# встановити Symfony Flex у середовищі CI
composer global config --no-plugins allow-plugins.symfony/flex true
composer global require --no-progress --no-scripts --no-plugins symfony/flex

# встановити залежності (використання --prefer-dist та --no-progress рекомендовано
# для того, щоб мати краще виведення та швидший час завантаження)
composer update --prefer-dist --no-progress

Caution

Якщо ви хочете кешувати ваші залежності Composer, не кешуйте каталог vendor/, так як є має побічні дії. Натомість кешуйте $HOME/.composer/cache/files.

Установка

Пакети мають встановлювати "type": "symfony-bundle" в їхньому файлі composer.json. Таким чином, Symfony Flex зможе автоматично вмикати ваш пакет, коли його встановлено.

Якщо ваш пакет вимагає будь-якого налаштування (наприклад, конфігурації, нових файлів, змін .gitignore та ін.), то вам потрібно створити рецепт Symfony Flex.

Документація

Всі класи та функції мають постачатися з повним PHPDoc.

Розширена документація також має бути представлена у каталозі Resources/doc/. Індексний файл (наприклад, docs/index.rst або docs/index.md) - це єдиний обовʼязковий файл, який має бути точкою входу для документації reStructuredText (rST) - це формат, який використовується для відображення документації на symfony.com.

Инструкції по встановленню

Для того, щоб полегшити встановлення сторонніх пакетів, розгляньте використання наступних стандартизованих інструкцій в файлі README.md.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Установка
=========

Переконайтеся в тому, що Composer встановлено глобально, як пояснюється
у [главі про установку](https://getcomposer.org/doc/00-intro.md) документації
Composer.

Додатки, що використовують Symfony Flex
---------------------------------------

Відкрийте консоль команд, введіть каталог вашого проекту та виконайте:

```console
$ composer require <package-name>
```

Додатки, що не використовують Symfony Flex
------------------------------------------

### Крок 1: Завантажте пакет

Відкрийте консоль команд, введіть каталог вашого проекту та виконайте
наступну команду, щоб завантажити останню стабільну версію цього пакета:

```console
$ composer require <package-name>
```

### Крок 2: Підключіть пакет

Далі, підключіть пакет, додавши його до списку зареєстрованих пакетів
у файлі `config/bundles.php` вашого проекту:

```php
// config/bundles.php

return [
    // ...
    <vendor>\<bundle-name>\<bundle-long-name>::class => ['all' => true],
];
```

Приклад вище передбачає, що ви встановлюєте останню стабільну версію пакета, де ви не повинні надавати номер версії пакета (наприклад, composer require friendsofsymfony/user-bundle).
Якщо інструкції з установки посилаються на застарілу або нестабільну версію пакета, увімкніть обмеження версій (наприклад, composer require friendsofsymfony/user-bundle "~2.0@dev").

За бажанням ви можете додати більше кроків установки (Крок 3, Крок 4, і т.д.), щоб пояснити інші необхідні для установки завдання, як, наприклад, реєстрація маршрутів або скидання ресурсів.

Маршрутизація

Якщо пакет надає маршрути, вони повинні мати префікс у вигляді додаткового імені пакета. Наприклад, якщо ваш пакет називається AcmeBlogBundle, всі його маршрути повинні мати префікс acme_blog_.

Шаблони

Якщо пакет надає шаблони, то вони мають використовувати Twig. Пакет не повинен надавати головний макет, окрім випадків, коли він надає повністю робочий додаток.

Файли перекладу

Якщо пакет надає переклади повідомлень, вони повинні бути визначені у форматі XLIFF; домен повинен бути названий за іменем пакета (acme_blog).

Пакет не повинен перевизначати існуючі повідомлення з іншого пакета.

Конфігурація

Щоб надати більшу гнучкість, пакет може надати конфігуровані налаштування, використовуючи вбудовані механізми Symfony.

Для простих налаштувань конфігурації, покладайтеся на запис parameters, який за замовчуванням знаходиться у конфігурації Symfony. Параметри Symfony - це прості пари ключ/значення; значння може бути будь-яким валідним PHP-значеням. Кожне імʼя параметра повинно починатися з додаткового імені пакета, хоча це всього лише пропозиція кращих практик. Решта імені параметра використовуватиме крапку (.) для розділення різних частин (наприклад, acme_blog.author.email).

Кінцевий користувач можое надавати значення в будь-якому файлі конфігурації:

1
2
3
# config/services.yaml
parameters:
    acme_blog.author.email: 'fabien@example.com'

Вилучте параметри конфігурації у вашому коді з контейнера:

1
$container->getParameter('acme_blog.author.email');

Навіть якщо цей механізм достатньо простий, вам слід розглянути використання більщ просунутої семантичної конфігурації пакета, щоб зробити вашу конфігурацію більш міцною.

Версіонування

Пакети повинні бути версіоновані, слідуючи Стандартам семантичного версіонування.

Сервіси

Якщо пакет визначає сервіси, то вони повинні мати префікс у вигляді додаткового імені пакета, замість використання повністю кваліфікованих імен класів, як ви це робите у ваших сервісах проекту. Наприклад, сервіси AcmeBlogBundle повинні мати префікс acme_blog. Причина в тому, що пакети не повинні покладатися на функції на кшталт автомонтування або автоконфігурації, щоб не створити надмірне навантаження при компіляції сервісів додатку.

На додаток, сервіси, які не повинні використовуватися додатками напряму, повинні бути визначені, як приватні . Для публічних сервісіі повинні бути створені додаткові імена з інтерфейса / класу в id сервісу. Наприклад, в MonologBundle, додаткове імʼя створюється з Psr\Log\LoggerInterface в logger, щоб типізування LoggerInterface можна було використовувати для автомонтування.

Сервіси не повинні використовувати автомонтування або автоконфігурацію. Замість цього, всі сервіси повинні бути чітко визначені.

Tip

Якщо ви не плануєте використання ідентифікатора сервісу кінцевим користувачем, ви можете позначити його як прихований, додавши до нього крапку на початку (наприклад, .acme_blog.logger). Це запобігатиме відображенню сервісу у виведенні команди debug:container за замовчуванням.

See also

Ви можете дізнатися набагато більше про завантаження сервісів в пакеті, прочитавши цю статтю: Як завантажувати конфігурацію сервісу всередині пакета.

Метадані Composer

Файл composer.json повинен включати в себе як мінімум наступні метадані:

name
Складається з постачальника та короткого імені пакета. Якщо ви реалізуєте пакет особисто, а не від імені компанії, використовуйте своє імʼя (наприклад, johnsmith/blog-bundle). Виключте імʼя постачальника з короткого імені пакета та розділіть всі слова дефісами. Наприклад: AcmeBlogBundle перетворюється на blog-bundle, а AcmeSocialConnectBundle - на social-connect-bundle.
description
Коротке пояснення цілі пакета.
type
Використовує значення symfony-bundle.
license
Рядок (або масив рядків) з валідним ідентифікатором ліцензії, на кшталт MIT.
autoload

Ця інформація використовується Symfony для завантаження класів пакета. Рекомендується використовувати стандарт автозавантаження PSR-4: використайте простір імен як ключ, а локацію головного класу пакета (відносно до composer.json) - як значення. Так як головний класс знаходиться у каталозі пакета src/:

1
2
3
4
5
6
7
8
9
10
11
12
{
    "autoload": {
        "psr-4": {
            "Acme\\BlogBundle\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Acme\\BlogBundle\\Tests\\": "tests/"
        }
    }
}

Для того, щоб полегшити розробникам пошук вашого пакета, зареєструйте його в Packagist, офіційному сховищі пакетів для Composer.

Джерела

Якщо пакет посилається на якісь джерела (файли конфігурації, файли перекладу тощо), ви можете використовувати фізичні шляхи (наприклад, __DIR__/config/services.xml).

Раніше ми рекомендували використовувати тільки логічні шляхи (наприклад @AcmeBlogBundle/config/services.xml) і розвʼязувати їх за допомогою локатора ресурсів , що надається ядром Symfony, але це більше не є рекомендованою практикою.