У меня есть три старых приложения (работающих на Symfony 2), каждое из которых было разработано в отдельных репозиториях git и сконфигурировано в их соответствующих vhosts:
company.com
Вебсайт компании.admin.company.com
Администрация сайта.api.company.com
API сервис компании.Хотя они и используют одну и ту же базу данных. Поэтому мы решили (Компания) объединить их всех в одном приложении со структурой Symfony 4 & Подход, в основном, для удаления большого количества дублированных данных и улучшения их обслуживания.
Прямо сейчас я интегрирую все в одном приложении / репозитории, как было запланировано, но я начинаю иметь дело с некоторой производительностью & вопросы структуры:
index.php
Я сделал два префикса маршрутов, чтобы иметь доступ к company.com/admin/
а также company.com/api/
вспомогательное приложение, поэтому все маршруты загружаются каждый раз 🙁SonataAdminBundle
тоже грузится sadЯ хотел бы сохранить ранний vhost и загрузить только необходимые пакеты и конфигурацию для доменов:
company.com
Загрузка пакетов, маршрутов и конфигурации только для веб-сайта компании (SwiftmailerBundle
…)admin.company.com
Загружает пакеты, маршруты и конфигурацию только для администрирования сайта (SecurityBundle
, SonataAdminBundle
…)api.company.com
Загружает только пакеты, маршруты и конфигурацию, чтобы обеспечить быстрый сервис API компании (SecurityBundle
, FOSRestBundle
, NelmioApiDocBundle
…)Это то, что я делаю до сих пор:
// public/index.php
// ...
$request = Request::createFromGlobals();
$kernel = new Kernel(getenv('APP_ENV'), getenv('APP_DEBUG'));
// new method implemented in my src/kernel.php
$kernel->setHost($request->server->get('HTTP_HOST'));
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Я проверил текущий префикс хоста в Kernel::registerBundles()
метод, и я загрузил только необходимые пакеты, но все еще у меня проблемы с bin/console
файл (он не работает как HTTP_HOST
переменная не определена для CLI) Я хотел бы очистить кэш для каждого «подпрограммы» и так далее.
Я проводил некоторые исследования по этой теме, но до сих пор не смог найти ничего полезного для своего сценария (Symfony 4).
Возможно ли иметь много приложений в одном репозитории проектов, работающих независимо (например, отдельные приложения), но совместно использовать некоторую конфигурацию? Каков наилучший подход для достижения этого?
Заранее спасибо.
Скорее всего несколько ядер подход мог бы стать хорошим вариантом для решения такого рода проектов, но теперь, если рассмотреть подход Symfony 4 с переменными среды, структурой и реализацией ядра, его можно улучшить.
Основанное на имени виртуальное ядро
Термин «виртуальное ядро» относится к практике запуска более одного приложения (например, api.example.com
а также admin.example.com
) в одном репозитории проекта. Виртуальные ядра «основаны на именах», что означает, что в каждом приложении работает несколько имен ядер. Тот факт, что они работают в одном и том же физическом репозитории проекта, не очевиден для конечного пользователя.
Короче говоря, каждое имя ядра соответствует одному приложению.
Конфигурация на основе приложения
Во-первых, вам нужно будет повторить структуру одного приложения для config
, src
, var
каталоги и оставить корневую структуру для общих пакетов и конфигурации. Это должно выглядеть так:
├── config/
│ ├── admin/
│ │ ├── packages/
│ │ ├── bundles.php
│ │ ├── routes.yaml
│ │ ├── security.yaml
│ │ └── services.yaml
│ ├── api/
│ ├── site/
│ ├── packages/
│ ├── bundles.php
├── src/
│ ├── Admin/
│ ├── Api/
│ ├── Site/
│ └── VirtualKernel.php
├── var/
│ ├── cache/
│ │ ├── admin/
│ │ │ └── dev/
│ │ │ └── prod/
│ │ ├── api/
│ │ └── site/
│ └── log/
Далее, используя Kernel::$name
свойство, которое вы можете выделить приложение для запуска с выделенными файлами проекта (var/cache/<name>/<env>/*
):
<name><Env>DebugProjectContainer*
<name><Env>DebugProjectContainerUrlGenerator*
<name><Env>DebugProjectContainerUrlMatcher*
Это будет ключом к производительности, поскольку каждое приложение по определению имеет свой собственный контейнер DI, маршруты и файлы конфигурации. Вот полный образец VirtualKernel
класс, который поддерживает предыдущую структуру:
SRC / VirtualKernel.php
// WITHOUT NAMESPACE!
use Symfony\Component\HttpKernel\Kernel;
class VirtualKernel extends Kernel
{
use MicroKernelTrait;
private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
public function __construct($environment, $debug, $name)
{
$this->name = $name;
parent::__construct($environment, $debug);
}
public function getCacheDir(): string
{
return $this->getProjectDir().'/var/cache/'.$this->name.'/'.$this->environment;
}
public function getLogDir(): string
{
return $this->getProjectDir().'/var/log/'.$this->name;
}
public function serialize()
{
return serialize(array($this->environment, $this->debug, $this->name));
}
public function unserialize($data)
{
[$environment, $debug, $name] = unserialize($data, array('allowed_classes' => false));
$this->__construct($environment, $debug, $name);
}
public function registerBundles(): iterable
{
$commonBundles = require $this->getProjectDir().'/config/bundles.php';
$kernelBundles = require $this->getProjectDir().'/config/'.$this->name.'/bundles.php';
foreach (array_merge($commonBundles, $kernelBundles) as $class => $envs) {
if (isset($envs['all']) || isset($envs[$this->environment])) {
yield new $class();
}
}
}
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$container->setParameter('container.dumper.inline_class_loader', true);
$this->doConfigureContainer($container, $loader);
$this->doConfigureContainer($container, $loader, $this->name);
}
protected function configureRoutes(RouteCollectionBuilder $routes): void
{
$this->doConfigureRoutes($routes);
$this->doConfigureRoutes($routes, $this->name);
}
private function doConfigureContainer(ContainerBuilder $container, LoaderInterface $loader, string $name = null): void
{
$confDir = $this->getProjectDir().'/config/'.$name;
if (is_dir($confDir.'/packages/')) {
$loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
}
if (is_dir($confDir.'/packages/'.$this->environment)) {
$loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
}
$loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob');
if (is_dir($confDir.'/'.$this->environment)) {
$loader->load($confDir.'/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
}
}
private function doConfigureRoutes(RouteCollectionBuilder $routes, string $name = null): void
{
$confDir = $this->getProjectDir().'/config/'.$name;
if (is_dir($confDir.'/routes/')) {
$routes->import($confDir.'/routes/*'.self::CONFIG_EXTS, '/', 'glob');
}
if (is_dir($confDir.'/routes/'.$this->environment)) {
$routes->import($confDir.'/routes/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
}
$routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/', 'glob');
}
}
Теперь ваш \VirtualKernel
класс требует дополнительного аргумента (name
), который определяет приложение для загрузки. Чтобы автозагрузчик нашел ваш новый \VirtualKernel
класс, не забудьте добавить его в composer.json
раздел автозагрузки:
"autoload": {
"classmap": [
"src/VirtualKernel.php"],
"psr-4": {
"Admin\\": "src/Admin/",
"Api\\": "src/Api/",
"Site\\": "src/Site/"}
},
Затем беги composer dump-autoload
сбросить новый конфиг автозагрузки.
Сохранение единой точки входа для всех приложений
├── public/
│ └── index.php
Следуя той же философии Symfony 4, в то время как переменные среды решают, какую среду разработки и режим отладки следует использовать для запуска вашего приложения, вы можете добавить новую APP_NAME
Переменная окружения для установки приложения на выполнение:
общественности / index.php
// ...
$kernel = new \VirtualKernel(getenv('APP_ENV'), getenv('APP_DEBUG'), getenv('APP_NAME'));
// ...
Сейчас вы можете поиграть с ним, используя встроенный веб-сервер PHP, добавив префикс новой переменной среды приложения:
$ APP_NAME=site php -S 127.0.0.1:8000 -t public
$ APP_NAME=admin php -S 127.0.0.1:8001 -t public
$ APP_NAME=api php -S 127.0.0.1:8002 -t public
Выполнение команд для приложения
├── bin/
│ └── console.php
Добавить новый параметр консоли --kernel
чтобы иметь возможность запускать команды из разных приложений:
бен / консоль
// ...
$name = $input->getParameterOption(['--kernel', '-k'], getenv('APP_NAME') ?: 'site');
//...
$kernel = new \VirtualKernel($env, $debug, $name);
$application = new Application($kernel);
$application
->getDefinition()
->addOption(new InputOption('--kernel', '-k', InputOption::VALUE_REQUIRED, 'The kernel name', $kernel->getName()))
;
$application->run($input);
Позже используйте эту опцию для запуска любой команды, отличной от команды по умолчанию (site
).
$ bin/console about -k=api
Или, если хотите, используйте переменные окружения:
$ export APP_NAME=api
$ bin/console about # api application
$ bin/console debug:router # api application
$
$ APP_NAME=admin bin/console debug:router # admin application
Также вы можете настроить по умолчанию APP_NAME
переменная среды в .env
файл.
Запуск тестов для одного приложения
├── tests/
│ ├── Admin/
│ │ └── AdminWebTestCase.php
│ ├── Api/
│ ├── Site/
tests
каталог очень похож на src
каталог, просто обновите composer.json
сопоставить каждый каталог tests/<Name>/
с его пространством имен PSR-4:
"autoload-dev": {
"psr-4": {
"Admin\\Tests\\": "tests/Admin/",
"Api\\Tests\\": "tests/Api/",
"Site\\Tests\\": "tests/Site/"}
},
Опять беги composer dump-autoload
заново сгенерировать конфигурацию автозагрузки.
Здесь вам может понадобиться создать <Name>WebTestCase
класс для каждого приложения, чтобы выполнить все тесты вместе:
Тест / Admin / AdminWebTestCase
namespace Admin\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
abstract class AdminWebTestCase extends WebTestCase
{
protected static function createKernel(array $options = array())
{
return new \VirtualKernel(
isset($options['environment']) ? $options['environment'] : 'test',
isset($options['debug']) ? $options['debug'] : true,
'admin'
);
}
}
Позже распространяется от AdminWebTestCase
тестировать admin.company.com
приложение (сделать то же самое для других).
Производство и вхосты
Установите переменную среды APP_NAME
для каждой конфигурации vhost на вашем производственном сервере и машине разработки:
<VirtualHost company.com:80>
SetEnv APP_NAME site
# ...
</VirtualHost>
<VirtualHost admin.company.com:80>
SetEnv APP_NAME admin
# ...
</VirtualHost>
<VirtualHost api.company.com:80>
SetEnv APP_NAME api
# ...
</VirtualHost>
Добавление дополнительных приложений в проект
С помощью трех простых шагов вы сможете добавить новые vKernel / приложения в текущий проект:
config
, src
а также tests
каталоги новой папки с <name>
приложения и его содержание.config/<name>/
по крайней мере, bundles.php
файл.composer.json
autoload / autoload-dev разделяет новые пространства имен PSR-4 для src/<Name>/
а также tests/<Name>
каталоги и обновить файл конфигурации автозагрузки.Проверьте, работает ли новое приложение bin/console about -k=<name>
,
Конечная структура каталогов:
├── bin/
│ └── console.php
├── config/
│ ├── admin/
│ │ ├── packages/
│ │ ├── bundles.php
│ │ ├── routes.yaml
│ │ ├── security.yaml
│ │ └── services.yaml
│ ├── api/
│ ├── site/
│ ├── packages/
│ ├── bundles.php
├── public/
│ └── index.php
├── src/
│ ├── Admin/
│ ├── Api/
│ ├── Site/
│ └── VirtualKernel.php
├── tests/
│ ├── Admin/
│ │ └── AdminWebTestCase.php
│ ├── Api/
│ ├── Site/
├── var/
│ ├── cache/
│ │ ├── admin/
│ │ │ └── dev/
│ │ │ └── prod/
│ │ ├── api/
│ │ └── site/
│ └── log/
├── .env
├── composer.json
В отличие от несколько файлов ядра подход, эта версия уменьшает много дублирования кода и файлов; только одно ядро, index.php
а также console
для всех приложений, благодаря переменным среды и классу виртуального ядра.
Пример на основе скелета Symfony 4: https://github.com/yceruto/symfony-skeleton-vkernel
Вдохновленный в https://symfony.com/doc/current/configuration/multiple_kernels.html
Вы можете создавать новые среды, такие как: admin
, website
, api
, Затем предоставьте переменную среды SYMFONY_ENV
с помощью apache / nginx вы сможете запускать специализированное приложение и по-прежнему использовать субдомены company.com
, admin.company.com
, api.company.com
, Также вы сможете легко загрузить только необходимую маршрутизацию.
В зависимости от того, сколько приложений вы хотите создать на основе этого подхода, вы можете добавить условия для загрузки указанных пакетов по проекту в AppKernel
класс или создать отдельные классы для каждого проекта.
Вы также должны прочитать эту статью https://jolicode.com/blog/multiple-applications-with-symfony2
Также, если вы хотите запустить тестирование Behat, вы должны запустить его с помощью этой команды:
для окон:
set APP_NAME=web&& vendor\bin\behat
для Linux:
export APP_NAME='web' && vendor\bin\behat
где «web» — это имя вашего ядра, которое вы хотите запустить.