多内核方法可能是解决此类项目的一个不错的选择,但现在考虑在 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 的相同 filosofy,而环境变量决定了应该使用哪个开发环境和调试模式来运行您的应用程序,您可以添加一个新的APP_NAME
环境变量来设置要执行的应用程序:
public/index.php
// ...
$kernel = new \VirtualKernel(getenv('APP_ENV'), getenv('APP_DEBUG'), getenv('APP_NAME'));
// ...
现在,您可以使用 PHP 的内置 Web 服务器来使用它,并为新的应用程序环境变量添加前缀:
$ 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
,以便能够从不同的应用程序运行命令:
bin/console
// ...
$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
类,以便一起执行所有测试:
test/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
文件添加到目录。
- 将新的 PSR-4 命名空间和目录添加到
composer.json
autoload/autoload-dev 部分,并更新自动加载配置文件。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