Лучшие практики для повторно используемых пакетов

Дата обновления перевода 2022-12-06

Лучшие практики для повторно используемых пакетов

Эта статья полностью раскрывает то, как структурировать ваши повторно используемые пакеты так, чтобы их было легко конфигурировать и расширять. Повторно используемые пакеты - это те пакеты, которые должны распространяться приватну между множеством проектов компании, или публично, чтобы любой проект 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
/
├── 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, но вы можете выбрать любую лицензию;
  • Resources/doc/index.rst: Корневой файл для документации Пакета.

Глубина под-каталогов должна быть минимальной для большинства используемых классов и файлов. Два уровня - это максимум.

Каталог пакета только для чтения. Если вам нужно написать временные файлы, храните их в каталоге cache/ или log/ хоста приложения. Инструменты могут генерировать файлы в структуре каталога пакета, но только, если сгенерированные файлы будут частью хранилища.

Следующие классы и файлы имеют специфические месторасположения (некоторые обязательны, а другие являются просто соглашениями, которым следует большинство разработчиков):

??? ???????
??????? src/Command/
??????????? src/Controller/
?????????? ??????-?????????? src/DependencyInjection/
???????? Doctrine ORM (?? ????????? ?????????) src/Entity/
????????? Doctrine ODM (?? ????????? ?????????) src/Document/
????????? ??????? src/EventListener/
???????????? (????????, ???????, ? ??.) config/
???-??????? (CSS, JS, ???????????) public/
????? ????????? 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, хранящиеся в Resources/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.

Расширенная документация также должна быть предоставлена в каталоге docs/. Индексный файл (например, docs/index.rst или docs/index.md) - это единственный обязательный файл, который должен быть точкой входа для документациию reStructuredText (rST) - это формат, используемый для отображения документации на веб-сайте Symfony.

Инструкции по установке

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

  • Markdown
  • RST
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
42
Установка
=========

Убедитесь в том, что 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).

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

  • YAML
  • XML
  • PHP
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 можно было использовать для автомонтирования.

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

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).

Логические пути необходимы из-за механизма переопределения пакетов, который позволяет вам переопределять любой источник или файл любого пакета. См. , чтобы узнать больше о преобразовании физических путей в логические.

Имейте в виду, что шаблоны используют упрощённую версию логического пути, показанного выше. Например, на шаблон index.html.twig, находящийся в каталоге templates/Default/ FooBundle, ссылаются так: @AcmeBlog/Default/index.html.twig.