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

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

Имя пакета

Пакет также является PHP пространством имён. Пространство имени должно следовать стандарту совместимости или PSR-4 для PHP пространств имён и имён классов: оно должно начинаться с сегмента поставщика, затем идет ноль или другие сегменты категорий, и заканчивается оно коротким именем пространства имён, которое должно заканчиваться суффиксом 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
<your-bundle>/
├─ AcmeBlogBundle.php
├─ Controller/
├─ README.md
├─ LICENSE
├─ Resources/
│   ├─ config/
│   ├─ doc/
│   │  └─ index.rst
│   ├─ translations/
│   ├─ views/
│   └─ public/
└─ Tests/

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

  • AcmeBlogBundle.php: Это класс, трансформирующий простой каталог в пакет Symfony (измените его на имя вашего пакета);
  • README.md: Этот файл содержит базовое описание пакета и обычно показывает базовые примеры и ссылки на полную документацию (может использовать любые форматы разметки, поддерживаемые GitHub, например, README.rst);
  • LICENSE: Полное содержимое лицензии, используемой кодом. Большинство сторонних пакетов публикуются под лицензией MIT, но вы можете `выбрать любую лицензию`_;
  • Resources/doc/index.rst: The root file for the Bundle documentation.

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

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

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

Тип Каталог
Команды Command/
Контроллеры Controller/
Расширения сервис-контейнера DependencyInjection/
Сущности Doctrine ORM (не используя аннотации) Entity/
Документы Doctrine ODM (не используя аннотации) Document/
Слушатели событий EventListener/
Конфигурация (маршруты, сервисы, и др.) Resources/config/
Веб-ресурсы (CSS, JS, изображения) Resources/public/
Файлы переводов Resources/translations/
Валидация (не используя аннотации) Resources/config/validation/
Сериализация (не используя аннотации) Resources/config/serialization/
Шаблоны Resources/views/
Модульные и функциональные тесты Tests/

Классы

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

Все классы и файлы должны следовать стандартам разработки кода Symfony.

Некторые классы должны рассматриваться как фасады и должны быть максимально короткими, например, Команды, Помощники, Слушатели и Контроллеры.

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

Классы исключений должны храниться в под-пространстве имён Exception.

Поставщики

Пакет также не должен включать в себя сторонние PHP-библиотеки. Он должен полагаться на стандартную автозагрузку Symfony вместо этого.

Пакет не должен включать в себя сторонние библиотеки, написанные на JavaScript, CSS или любых других языках.

Тесты

Пакет должен поставляться с комплектом тестов, написанных с PHPUnit и хранящихся в каталоге Tests/. Тесты должны следовать следующим принципам:

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

Note

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

Непрерывная интеграция

Непрерывное тестирование кода пакета, включая всего фиксации и запросы на включение, это хорошая практика под названием "Непрерывная интеграция". Существует несколько сервисов, предоставляющих эту функцию бесплатно для проектов с открытым исходным кодом. Наиболее популярный сервис для пакетов Symfony называется Travis CI.

Вот рекомендованный файл конфигурации (.travis.yml) для пакетов Symfony, которые тестируют две последних LTS версии Symfony и самый последний бета-релиз:

 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
language: php
sudo: false
cache:
    directories:
        - $HOME/.composer/cache/files
        - $HOME/symfony-bridge/.phpunit

env:
    global:
        - PHPUNIT_FLAGS="-v"
        - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"

matrix:
    fast_finish: true
    include:
          # Минимальное количество поддерживаемых зависимостей с наиболее новой и старой PHP версией
        - php: 7.2
          env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
        - php: 7.0
          env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"

          # Протестировать последний стабильный релиз
        - php: 7.0
        - php: 7.1
        - php: 7.2
          env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text"

          # Протестировать LTS версии. Это гарантирует, что мы не использует пакеты Symfony с
          # версией, старше 2 или 3 соответственно. Прочтите больше на https://github.com/symfony/lts
        - php: 7.2
          env: DEPENDENCIES="symfony/lts:^2"
        - php: 7.2
          env: DEPENDENCIES="symfony/lts:^3"

          # Последняя фиксация в главном
        - php: 7.2
          env: STABILITY="dev"

    allow_failures:
          # Dev-master может быть неуспешным.
        - env: STABILITY="dev"

before_install:
    - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
    - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi;
    - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi;

install:
    # Будет удалено, когда будет решена эта проблема: https://github.com/composer/composer/issues/5355
    - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
    - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
    - ./vendor/bin/simple-phpunit install

script:
    - composer validate --strict --no-check-lock
    # simple-phpunit это оболочка PHPUnit, предоставленная компонентом PHPUnit Bridge и
    # она помогает с тестированием кода наследования и устареваний (composer требует symfony/phpunit-bridge)
    - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS

Рассмотрите также использование Travis cron, чтобы убедиться, что ваш проект будет построен, даже если нет новых запросов или фиксаций.

Установка

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

Если ваш пакет требует какой-либо настройки (например, конфигурации, новых файлов, изменений .gitignore, и др.), то вам нужно создать рецепт Symfony Flex.

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

Все классы и функции должны поставляться с полным PHPDoc.

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

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

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

  • Markdown
     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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    Установка
    =========
    
    Приложения, использующие Symfony Flex
    -------------------------------------
    
    Откройте консоль команд, введите каталог вашего проекта и выполните:
    
    ```console
    $ composer require <package-name>
    ```
    
    Приложения, не использующие Symfony Flex
    ----------------------------------------
    
    ### Шаг 1: Скачайте пакет
    
    Откройте консоль команд, введите каталог вашего проекта и выполните
    следующую команду, чтобы скачать последнюю стабильную версию этого
    пакета:
    
    ```console
    $ composer require <package-name>
    ```
    ```
    
    Эта команда требует, чтобы у вас был глобально установлен Composer, как
    объясняется в [главе об установке](https://getcomposer.org/doc/00-intro.md)
    документации Composer.
    
    ### Шаг 2: Подключите пакет
    
    Далее, подключите пакет, добавив его к списку зарегистрированных пакетов
    в файле `app/AppKernel.php` вашего проекта:
    
    ```php
    <?php
    // app/AppKernel.php
    
    // ...
    class AppKernel extends Kernel
    {
        public function registerBundles()
        {
            $bundles = array(
                // ...
                new <vendor>\<bundle-name>\<bundle-long-name>(),
            );
    
            // ...
        }
    
        // ...
    }
    ```
    
  • reStructuredText
     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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    Установка
    =========
    
    Приложения, использующие Symfony Flex
    -------------------------------------
    
    Откройте консоль команд, введите каталог вашего проекта и выполните:
    
    .. code-block:: bash
    
        $ composer require <package-name>
    
    Приложения, не использующие Symfony Flex
    ----------------------------------------
    
    Шаг 1: Скачайте пакет
    ~~~~~~~~~~~~~~~~~~~~~
    
    Откройте консоль команд, введите каталог вашего проекта и выполните
    следующую команду, чтобы скачать последнюю стабильную версию этого
    пакета:
    
    .. code-block:: terminal
    
        $ composer require <package-name>
    
    Эта команда требует, чтобы Composer был установлен у вас глобально, как
    объясняется в `главе об установке`_ документации Composer.
    
    Шаг 2: Подключите пакет
    ~~~~~~~~~~~~~~~~~~~~~~~
    
    Далее, подключите пакет, добавив его к списку зарегистрированных пакетов
    в файле ``app/AppKernel.php`` вашего проекта:
    
    .. code-block:: php
    
        <?php
        // app/AppKernel.php
    
        // ...
        class AppKernel extends Kernel
        {
            public function registerBundles()
            {
                $bundles = array(
                    // ...
    
                    new <vendor>\<bundle-name>\<bundle-long-name>(),
                );
    
                // ...
            }
    
            // ...
        }
    
    .. _`глава об установке`: https://getcomposer.org/doc/00-intro.md
    

Пример выше предполагает, что вы устанаваливаете последнюю стабильную версию пакета, где вы не должны предоставлять номер версии пакета (например, 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
    1
    2
    3
    # app/config/config.yml
    parameters:
        acme_blog.author.email: '[email protected]'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <parameters>
            <parameter key="acme_blog.author.email">[email protected]</parameter>
        </parameters>
    
    </container>
    
  • PHP
    1
    2
    // app/config/config.php
    $container->setParameter('acme_blog.author.email', '[email protected]');
    

Извлеките параметры конфигурации в вашем коде из контейнера:

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

Даже если этот механизм достаточно прост, вам стоит рассмотреть использование более продвиутой семантической конфигурации пакета.

Контроль версий

Пакеты должны быть версионированы, следуя Стандартам семантического версионирования.

Сервисы

Если пакет определяет сервисы, то они должны иметь префикс в виде дополнительного имени пакета. Например, сервисы AcmeBlogBundle должны иметь префикс acme_blog.

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

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

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

Метаданные Composer

Файл composer.json должен включать в себя как минимум следующие метаданные:

name
Состоит из поставщика и короткого имени пакета. Если вы реализуете пакет лично, а не от имени компании, используйте ваше имя (например, johnsmith/blog-bundle). Исключите имя поставщика из короткого имени пакета и разделите все слова дефисами. Например: AcmeBlogBundle преобразуется в blog-bundle, а AcmeSocialConnectBundle - в social-connect-bundle.
description
Краткое разъяснение цели пакета.
type
Использует значение symfony-bundle.
license
строка (или массив строк) с `валидным идентификатором лицензии`_, вроде MIT.
autoload
Эта информация используется Symfony для загрузки классов пакета. Рекомендуется использовать стандарт автозагрузки PSR-4.

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

Источники

Если пакет ссылается на какие-либо источники (файлы конфигурации, переводов и др.), не используйте физические пути (например, __DIR__/config/services.xml), а используйте логические пути (например, @FooBundle/Resources/config/services.xml).

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

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.