Symfony 4

时间:2017-08-28 19:00:11

标签: php symfony symfony4 symfony-flex

我有三个旧的应用程序(在Symfony 2上运行),其中每个应用程序都是在独立的git存储库中开发的,并在各自的vhosts中配置:

  1. company.com公司网站。
  2. admin.company.com网站管理。
  3. api.company.com API公司服务。
  4. 即使他们共享同一个数据库。所以我们决定(公司)在一个应用程序中统一所有这些应用程序与Symfony 4结构&方法,主要是删除大量重复数据并改善其维护。

    现在,我正按照计划将所有应用程序/存储库集成在一起,但我开始处理一些性能和问题。结构问题:

    • 由于我只有一个入口点index.php,我做了两个路由前缀,以便能够访问company.com/admin/company.com/api/子应用,因此每次都会加载所有路由:(
    • 为每个请求不必要地加载和处理所有捆绑包和配置。例如:当我访问API路径时,SonataAdminBundle也被加载:(
    • 缓存清除命令需要很长时间才能完成。
    • 测试正在破裂,现在也需要很长时间才能完成。

    我想保留早期的虚拟主机并加载每个域所需的捆绑包和配置:

    1. company.com仅加载公司网站的捆绑包,路由和配置(SwiftmailerBundle,...)
    2. admin.company.com仅为网站管理加载捆绑包,路由和配置(SecurityBundleSonataAdminBundle,...)
    3. api.company.com仅加载捆绑包,路由和配置,以提供快速API公司服务(SecurityBundleFOSRestBundleNelmioApiDocBundle,...)
    4. 这就是我到目前为止所做的事情:

      // 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)。

      可以在一个项目存储库下独立运行许多应用程序(如个人应用程序)但共享一些配置吗?实现它的最佳方法是什么?

      提前致谢。

3 个答案:

答案 0 :(得分:10)

可能multiple kernels方法可能是解决此类项目的一个很好的选择,但现在考虑使用环境变量,结构和内核实现的Symfony 4方法,可以对其进行改进。

基于名称的虚拟内核

术语"虚拟内核"指的是在单个项目存储库上运行多个应用程序(例如api.example.comadmin.example.com)的做法。虚拟内核是基于名称的",这意味着您在每个应用程序上运行多个内核名称。它们在同一物理项目存储库上运行的事实对最终用户来说并不明显。

简而言之,每个内核名称对应一个应用程序。

基于应用程序的配置

首先,您需要复制configsrcvar目录的一个应用程序的结构,并保留共享包和配置的根结构。它应该是这样的:

├── 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的内置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,以便能够运行来自不同应用程序的命令:

仓/控制台

// ...
$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类,以便一起执行所有测试:

测试/管理/ 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应用程序(对其他应用程序执行相同操作)。

制作和vhosts

为生产服务器和开发机器中的每个vhost配置设置环境变量APP_NAME

<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 /应用程序添加到当前项目中:

  1. configsrctests目录中添加一个包含应用程序<name>及其内容的新文件夹。
  2. 至少添加config/<name>/目录bundles.php文件。
  3. 添加到composer.json autoload / autoload-dev,为src/<Name>/tests/<Name>目录分配新的PSR-4名称空间,并更新自动加载配置文件。
  4. 检查运行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.phpconsole

    基于Symfony 4骨架的示例:https://github.com/yceruto/symfony-skeleton-vkernel 灵感来自https://symfony.com/doc/current/configuration/multiple_kernels.html

答案 1 :(得分:1)

您可以创建新的环境,例如:adminwebsiteapi。然后通过apache / nginx提供环境变量SYMFONY_ENV,您将能够运行专用应用程序并仍使用子域company.comadmin.company.comapi.company.com。此外,您还可以轻松加载所需的路由。

取决于您希望基于此方法创建的应用程序数量,您可以添加条件以按AppKernel类中的项目加载指定的包,或为每个项目创建单独的类。

您还应该阅读这篇文章https://jolicode.com/blog/multiple-applications-with-symfony2

答案 2 :(得分:0)

此外,当您要运行Behat测试时,也应使用以下命令运行它:

对于Windows:

set APP_NAME=web&& vendor\bin\behat

对于Linux:

export APP_NAME='web' && vendor\bin\behat

其中“ web”是您要运行的内核名称。