
2024-05-10
Symfony 6 как создать свой бандл
2023-08-02
Symfony 6, как и все предыдущие ее версии, имеет систему бандлов. Это такие пакеты / библиотеки, разработанные специально для Symfony фреймворка, чтобы можно было легко подключать тот или иной функционал без проблем. По поводу бандлов есть много разногласий. Кто-то считает их злом, а кто-то добром, но факт в том, что они существуют и используются. Сегодня мы разберем как же создать свой собственный простенький бандл.
Создаем директорию lib/PassGeneratorBundle/src
Создаем сервис PassGenerator.php с неймспейсом PassGeneratorBundle;
Создаем тестовый контроллер в основной части проекта например Controller/Test.php
<?php
namespace App\Controller\Tools;
use App\Service\ToolsService;
use PassGeneratorBundle\PassGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/test')]
class Test extends AbstractController
{
#[Route('/1', name: 'test_1', methods: ['GET'])]
public function test1(PassGenerator $generator): Response
{
return $this->json([
'pass' => $generator->generatePassword()
]);
}
}
В тестовом контроллере создаем метод, который будет дергать что-то из бандла.
В PassGenerator пишем какую-нибудь логику по генерации паролей.
<?php
namespace PassGeneratorBundle;
class PassGenerator
{
public function generatePassword(): string
{
return $this->generatePasswordReal();
}
/**
* @param bool $numbers
* @param bool $upperCase
* @param bool $lowerCase
* @param bool $specialChars
* @param int $length
* @return string
*/
private function generatePasswordReal(
bool $numbers = true,
bool $upperCase = true,
bool $lowerCase = true,
bool $specialChars = true,
int $length = 10
): string {
$password = '';
$symbolsAvailable = [];
$numbersArr = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
$upperCaseArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$lowerCaseArr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
$specialCharsArr = ['!', '#', '$', '%', '&', '(', ')', '*', '+', '.', '/', ':', ';', '=', '>', '?', '@', '[', ']', '^', '`', '{', '|', '}', '~'];
if ($numbers) {
$symbolsAvailable = array_merge($symbolsAvailable, $numbersArr);
}
if ($upperCase) {
$symbolsAvailable = array_merge($symbolsAvailable, $upperCaseArr);
}
if ($lowerCase) {
$symbolsAvailable = array_merge($symbolsAvailable, $lowerCaseArr);
}
if ($specialChars) {
$symbolsAvailable = array_merge($symbolsAvailable, $specialCharsArr);
}
for ($i = 0; $i < $length; $i++) {
$password .= $symbolsAvailable[mt_rand(0, count($symbolsAvailable) - 1)];
}
return $password;
}
}
Далее, чтобы подцепился новый неймспейс - нужно в composer.json основного проекта добавить в директиву autoload наш новый неймспейс, чтобы можно было бы с ним работать локально (для начала).
"autoload": {
"psr-4": {
"PassGeneratorBundle\\": "lib/PassGeneratorBundle/src/",
"App\\": "src/"
}
},
После этого нам необхимодимо запустить команду composer dump-autoload, чтобы неймспейсы подцепились.
Если сейчас запустить контроллер - то мы увидем ошибку с текстом, о том, что мы не можем подцепить сервис - это происходит потому, что Symfony пока что не знает ничего про новый бандл и никак его предварительно не зарегистрировала. Чтобы это быстро пофиксить - мы добавим в файл services.yaml основного проекта строку, которая будет явно объявлять новый сервис.
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
PassGeneratorBundle\PassGenerator: ~
Теперь мы можем открыть страницу /test/1 нашего проекта и там мы увидем как уже генерируются пароли. Но пока что это еще не бандл.
Теперь нам необходимо в корне нашего бандла ( lib/PassGeneratorBundle/src/ ) создать класс PassGeneratorBundle.php со следующим содержимым:
<?php
namespace PassGeneratorBundle;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class PassGeneratorBundle extends AbstractBundle
{
}
И теперь мы можем зарегистрировать наш бандл как бандл в Symfony файле config/bundles.php добавив его по аналогии на последнюю строчку:
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
//...
PassGeneratorBundle\PassGeneratorBundle::class => ['all' => true],
];
Далее нам необходимо создать файл расширения DependencyInjection/PassGeneratorExtension.php
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
*/
public function load(array $configs, ContainerBuilder $container): void
{
}
}
А в PassGeneratorBundle.php добавить метод:
<?php
namespace PassGeneratorBundle;
use PassGeneratorBundle\DependencyInjection\PassGeneratorExtension;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class PassGeneratorBundle extends AbstractBundle
{
public function getContainerExtension(): ?ExtensionInterface
{
return new PassGeneratorExtension();
}
}
Теперь мы постепенно подходим к тому, что наш бандл уже является бандлом и мы хотим сделать его конфигурируемым.
Теперь нам необходимо создать файл config/services.yaml в корневой директории бандла со следующим содержимым:
parameters:
services:
_defaults:
autowire: true
public: false
autoconfigure: true
PassGeneratorBundle\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/PassGeneratorBundle.php'
PassGeneratorBundle\PassGenerator: ~
А из services.yaml основного проекта удалить строку, которая явно объявляла сервис PassGeneratorBundle\PassGenerator: ~
Теперь, чтобы у нас подцеплялся наш новый конфигурационный файл - нам нужно в Extension файле расписать метод load следующим образом:
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(dirname(__DIR__).'/config')
);
$loader->load('services.yaml');
}
}
После того как мы успешно подключили services.yaml нашего бандла - нам нужно задать нашему основному сервису алиас, чтобы в перспективе мы могли более гибко конфигурировать наш бандл. Для этого мы изменим services.yaml бандла следующим образом:
parameters:
services:
_defaults:
autowire: true
public: false
autoconfigure: true
PassGeneratorBundle\:
resource: '../'
exclude:
- '../DependencyInjection/'
- '../PassGeneratorBundle.php'
pgb.password_generator:
class: PassGeneratorBundle\PassGenerator
PassGeneratorBundle\PassGenerator: '@pgb.password_generator'
Теперь давайте создадим файл конфигурации нашего бандла в директории проекта по пути config/packages/password_generator.yaml ( не в бандле, а в проекте ).
password_generator:
passwordLength: 10
И доработаем наш Extension файл добавив метод getAlias в который пропишем название yaml'а
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(dirname(__DIR__).'/config')
);
$loader->load('services.yaml');
}
public function getAlias(): string
{
return 'password_generator';
}
}
Теперь, чтобы использовать конфигурирование бандла - нам надо определить а какие вообще параметры должны присутствовать у нашего бандла. Делается это с помощью файла DependencyInjection\Configuration.php со следующим содержимым:
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('password_generator');
$treeBuilder->getRootNode()
->children()
->integerNode('passwordLength')->defaultValue(15)->end()
->booleanNode('numbers')->defaultTrue()->end()
->booleanNode('upperCase')->defaultTrue()->end()
->booleanNode('lowerCase')->defaultTrue()->end()
->booleanNode('specialChars')->defaultTrue()->end()
->end()
;
return $treeBuilder;
}
}
В этом файле мы определили, что у нас будет несколько наборов конфигов, которые будут коррелировать с нашим файлом config/packages/password_generator.yaml. И давайте его тоже подредактируем, чтобы он полностью отражал все наши возможные настройки.
password_generator:
passwordLength: 10
numbers: true
upperCase: true
lowerCase: true
specialChars: false
Теперь, чтобы воспользоваться нашими конфигурационными файлами необходимо сделать доработку в класс расширения в методе load следующим образом:
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(dirname(__DIR__).'/config')
);
$loader->load('services.yaml');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs); //здесь будет лежать наш массив конфигов
}
public function getAlias(): string
{
return 'password_generator';
}
}
А это в свою очередь нам даёт возможность передавать эти конфиги внутрь нашего модуля в нужные сервисы следующим образом:
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(dirname(__DIR__).'/config')
);
$loader->load('services.yaml');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$definition = $container->getDefinition('pgb.password_generator');
$definition->setArgument('$numbers', $config['numbers']);
$definition->setArgument('$upperCase', $config['upperCase']);
$definition->setArgument('$lowerCase', $config['lowerCase']);
$definition->setArgument('$specialChars', $config['specialChars']);
$definition->setArgument('$length', $config['passwordLength']);
}
public function getAlias(): string
{
return 'password_generator';
}
}
<?php
namespace PassGeneratorBundle;
class PassGenerator
{
public function __construct(
private readonly bool $numbers,
private readonly bool $upperCase,
private readonly bool $lowerCase,
private readonly bool $specialChars,
private readonly int $length,
) {
}
public function generatePassword(): string
{
return $this->generatePasswordReal();
}
/**
* @return string
*/
private function generatePasswordReal(): string
{
$password = '';
$symbolsAvailable = [];
$numbersArr = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
$upperCaseArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
$lowerCaseArr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
$specialCharsArr = ['!', '#', '$', '%', '&', '(', ')', '*', '+', '.', '/', ':', ';', '=', '>', '?', '@', '[', ']', '^', '`', '{', '|', '}', '~'];
if ($this->numbers) {
$symbolsAvailable = array_merge($symbolsAvailable, $numbersArr);
}
if ($this->upperCase) {
$symbolsAvailable = array_merge($symbolsAvailable, $upperCaseArr);
}
if ($this->lowerCase) {
$symbolsAvailable = array_merge($symbolsAvailable, $lowerCaseArr);
}
if ($this->specialChars) {
$symbolsAvailable = array_merge($symbolsAvailable, $specialCharsArr);
}
for ($i = 0; $i < $this->length; $i++) {
$password .= $symbolsAvailable[mt_rand(0, count($symbolsAvailable) - 1)];
}
return $password;
}
}
Теперь у нас есть возможность переопределять конфигурации бандла внутри проекта пользовательскими ямлами, что делает бандл более расширяемым и конфигурируемым.
Чтобы продемонстрировать чуть больше возможностей внутри самого бандла давайте создадим PassContentInterface, который будет включать в себя методы: getNumbers(), getUpperCases(), getLowerCases(), getSpecialChars(). Каждый метод должен возвращать массив со строковыми символами для составления пароля. Также добавим класс PassContent и пробросим интерфейс в PasswordGenerator следующим образом:
<?php
namespace PassGeneratorBundle;
interface PassContentsInterface
{
/**
* @return string[]
*/
public function getNumbers(): array;
/**
* @return string[]
*/
public function getUpperCases(): array;
/**
* @return string[]
*/
public function getLowerCases(): array;
/**
* @return string[]
*/
public function getSpecialChars(): array;
}
<?php
namespace PassGeneratorBundle;
class PassContents implements PassContentsInterface
{
/**
* @return string[]
*/
public function getNumbers(): array
{
return ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
}
/**
* @return string[]
*/
public function getUpperCases(): array
{
return ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
}
/**
* @return string[]
*/
public function getLowerCases(): array
{
return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
}
/**
* @return string[]
*/
public function getSpecialChars(): array
{
return ['!', '#', '$', '%', '&', '(', ')', '*', '+', '.', '/', ':', ';', '=', '>', '?', '@', '[', ']', '^', '`', '{', '|', '}', '~'];
}
}
<?php
namespace PassGeneratorBundle;
class PassGenerator
{
public function __construct(
private readonly bool $numbers,
private readonly bool $upperCase,
private readonly bool $lowerCase,
private readonly bool $specialChars,
private readonly int $length,
private readonly PassContentsInterface $passContents
) {
}
/**
* @return string
*/
public function generatePassword(): string
{
$password = '';
$symbolsAvailable = [];
if ($this->numbers) $symbolsAvailable = array_merge($symbolsAvailable, $this->passContents->getNumbers());
if ($this->upperCase) $symbolsAvailable = array_merge($symbolsAvailable, $this->passContents->getUpperCases());
if ($this->lowerCase) $symbolsAvailable = array_merge($symbolsAvailable, $this->passContents->getLowerCases());
if ($this->specialChars) $symbolsAvailable = array_merge($symbolsAvailable, $this->passContents->getSpecialChars());
for ($i = 0; $i < $this->length; $i++) {
$password .= $symbolsAvailable[mt_rand(0, count($symbolsAvailable) - 1)];
}
return $password;
}
}
А также нам надо будет добавить этот новый сервис реализующий интерфейс в services.yaml нашего бандла:
parameters:
services:
_defaults:
autowire: true
public: false
autoconfigure: true
PassGeneratorBundle\:
resource: '../'
exclude:
- '../DependencyInjection/'
- '../PassGeneratorBundle.php'
pgb.default_pass_contents:
class: PassGeneratorBundle\PassContents
PassGeneratorBundle\PassContents: '@pgb.default_pass_contents'
pgb.password_generator:
class: PassGeneratorBundle\PassGenerator
arguments:
$passContents: '@pgb.default_pass_contents'
PassGeneratorBundle\PassGenerator: '@pgb.password_generator'
Теперь мы хотим дать пользователю бандла возможность подставлять свои символы для генерации пароля. И сделать он это может путем реализации собственного PassContentsInterface. Для того, чтобы дать пользователю эту возможность - добавим некоторые конфиги следующим образом:
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('password_generator');
$treeBuilder->getRootNode()
->children()
->integerNode('passwordLength')->defaultValue(15)->end()
->scalarNode('passContentsInterface')->defaultNull()->info('PassContentsInterface realisation should be passed here')->end()
->booleanNode('numbers')->defaultTrue()->end()
->booleanNode('upperCase')->defaultTrue()->end()
->booleanNode('lowerCase')->defaultTrue()->end()
->booleanNode('specialChars')->defaultTrue()->end()
->end()
;
return $treeBuilder;
}
}
Добавили passContentsInterface в дерево конфигураций.
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(dirname(__DIR__).'/config')
);
$loader->load('services.yaml');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$definition = $container->getDefinition('pgb.password_generator');
$definition->setArgument('$numbers', $config['numbers']);
$definition->setArgument('$upperCase', $config['upperCase']);
$definition->setArgument('$lowerCase', $config['lowerCase']);
$definition->setArgument('$specialChars', $config['specialChars']);
$definition->setArgument('$length', $config['passwordLength']);
if (!empty($config['passContentsInterface'])) {
$definition->setArgument('$passContents', new Reference($config['passContentsInterface']));
}
}
public function getAlias(): string
{
return 'password_generator';
}
}
Добавляем в Расширение в метод load проверку наличия конфига passContentesInterface и если он есть - то передаем в пассворд генератор класс определенный пользователем. А вот собственно и сам класс, расположенный src/Service/PassContents.php. Представим, что пользователь у нас захотел очень ограниченные пароли, поэтому реализовал вот такой вот интерфейс:
<?php
namespace App\Service;
use PassGeneratorBundle\PassContentsInterface;
class PassContents implements PassContentsInterface
{
/**
* @return string[]
*/
public function getNumbers(): array
{
return ['0', '1'];
}
/**
* @return string[]
*/
public function getUpperCases(): array
{
return ['A', 'Z'];
}
/**
* @return string[]
*/
public function getLowerCases(): array
{
return ['a', 'z'];
}
/**
* @return string[]
*/
public function getSpecialChars(): array
{
return ['@', '&'];
}
}
И добавляем референс на данный класс в конфиг проекта
password_generator:
passwordLength: 10
numbers: true
upperCase: true
lowerCase: true
specialChars: false
passContentsInterface: App\Service\PassContents
Также возможна еще одна в целом более корректная реализация в методе load внутри проверки без использования Reference класса:
<?php
namespace PassGeneratorBundle\DependencyInjection;
use Exception;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;
class PassGeneratorExtension extends Extension
{
/**
* @param array $configs
* @param ContainerBuilder $container
* @return void
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(dirname(__DIR__).'/config')
);
$loader->load('services.yaml');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$definition = $container->getDefinition('pgb.password_generator');
$definition->setArgument('$numbers', $config['numbers']);
$definition->setArgument('$upperCase', $config['upperCase']);
$definition->setArgument('$lowerCase', $config['lowerCase']);
$definition->setArgument('$specialChars', $config['specialChars']);
$definition->setArgument('$length', $config['passwordLength']);
if (!empty($config['passContentsInterface'])) {
$container->setAlias('pgb.default_pass_contents', $config['passContentsInterface']);
}
}
public function getAlias(): string
{
return 'password_generator';
}
}
Здесь мы установили на наш дефолтный алиас - алиас от пользователя внутри конструкции if (!empty($config['passContentsInterface'])) {
Теперь наш бандл готов, локально протестирован и готов к загрузке в репозиторий. На данный момент предполагается, что у вас есть навыки работы с git.
Первым делом вам необходимо создать репозиторий и инициализировать его в папке бандла.
cd lib/PassGeneratorBundle/
git init
git remote add origin git@gitflic.ru:username/bundle-name.git
git add .
git commit -m "Initial commit"
git push -u origin master
После того как мы запушили наш бандл в git - нам надо инициализировать композер с помощью команды composer init
Далее визард настройки предложит вам ответить на несколько вопросов и в итоге у вас должен получиться примерно такой вот composer.json файл:
{
"name": "otezvikentiy/pass-generator-bundle",
"description": "Symfony password generator bundle",
"type": "symfony-bundle",
"license": "MIT",
"autoload": {
"psr-4": {
"PassGeneratorBundle\\": "src/"
}
},
"authors": [
{
"name": "l.groshev",
"email": "OtezVikentiy@gmail.com",
"homepage": "https://otezvikentiy.tech",
"role": "Creator"
}
],
"minimum-stability": "stable",
"require": {
"php": "^8.1"
}
}
Теперь рассмотрим как подключить уже собранный бандл в наш проект используя composer основного проекта, а не папочку lib.
Для этого открываем composer.json основного проекта, убираем оттуда автоподгрузку неймспейсов бандла, которую мы делали в самом начале статьи.
"autoload": {
"psr-4": {
"PassGeneratorBundle\\": "lib/PassGeneratorBundle/src/", <-- удалить
"App\\": "src/"
}
},
И выполняем команду composer dump-autoload - после выполнения данной команды в корне основного проекта - наш бандл перестанет работать - это нормально.
Теперь мы можем подключить наш бандл через композер, используя нашу папку lib. Для этого в файл composer.json основного проекта надо дописать перед секцией require следующее:
"repositories": [
{
"type": "path",
"url": "lib/PassGeneratorBundle"
}
],
далее скопировать название нашего проекта из composer.json бандла из секции name и выполнить:
composer require otezvikentiy/pass-generator-bundle:dev-master
Теперь внутри нашей папки vendor основного проекта мы можем найти директорию otezvikentiy/pass-generator-bundle со всеми файлами нашего бандла. И теперь если мы обновим наш проект и зайдем на урл, который готовили в самом начале для проверки - то по урлу /test/1 - увидим онлайн генерацию паролей.
Теперь рассмотрим как подключать из репозитория наш бандл.
Чтобы повторно установить бандл, но уже из репозитория - нам нужно запустить команду
composer remove otezvikentiy/pass-generator-bundle
Эта команда удалит из папки vendor нашу папку с бандлом, но отработает с ошибкой, так как у нас остались конфигурационные файлы в проекте - это нормально, не стоит переживать сейчас об этом.
Теперь, чтобы скачивание нашего бандла шло с репозитория - нам необходимо в composer.json нашего основного проекта в разделе repositories указать вместо того, что мы указывали с type = path следующее:
"repositories": [
{
"type": "vcs",
"url": "https://gitflic.ru/project/otezvikentiy/pass-generator-bundle.git"
}
],
Снова запускаем команду установки нашей библиотеки
composer require otezvikentiy/pass-generator-bundle:dev-master
И теперь уже у нас устанавливается наш бандл из конкретного git-репозитория. Но этого все ещё не достаточно, ведь у нас осталась одна загвоздка - чтобы бандл устанавливался - нам нужно дополнительно прописывать репозиторий в composer.json, а это не очень удобно.
После того как мы откатали репозиторий и всё настроили - нам необходимо зайти на сайт packagist.org и там на главной странице будет инструкция, которая гласит, что для того, чтобы залить наш бандл в общий доступ - нам нужно:
1) запустить composer validate - убедиться, что composer.json валиден
2) https://packagist.org/packages/submit - проверить и опубликовать наш репозиторий
После этих действий мы можем спокойно удалять секцию repositories из нашего composer.json основного проекта и вызывать composer require спокойно на любом проекте - с этого момента у нас есть open source бандл для симфони, которым могут пользоваться и другие разработчики.
Также настоятельно рекомендуется тегировать вашу библиотеку-бандл, чтобы с ней было удобнее работать. Делать это можо за счет git tag команды.
Например вы сделали ряд полезных изменений в мастер ветке. Сделали git push. После этого вы можете в composer.json вашей библиотеки проставить version 1.0.3 например и выполнить команду гита:
git tag 1.0.3 && git push --tags
После этого зайти на packagist.org и нажать кнопку update в вашей библиотеке - в этот момент ваш новый тег появится в версиях на packagist и люди смогут использовать конкретную версию вашего бандла / библиотеки.
! ВНИМАНИЕ !
Это минимальная инструкиця. Каждая библиотека и бандл должны быть закрыты автотестами, а также должны выполняться рекомендации перечисленные здесь: https://symfony.com/doc/current/bundles/best_practices.html
Ура! Я наконец-то дописал статью как собирать собственные бандлы на Symfony 6!!!
Статья про EasyAdmin всё ещё в процессе )))
Не, ну мне же надо на чем-то тестировать твиттер локальный...
Я тут еще много полезного буду выкладывать, так что заходите обязательно почитать.
Сайтик пока что в разработке - это далеко не окончательная версия - по сути это то что удалось слепить за 8 часов.
Комментарии