feature #29 Proposal: Add autoconfiguration for Symfony 4, other tweaks (adiq)

This PR was merged into the 1.4-dev branch.

Discussion
----------

So this is my proposal for SymfonyExtension. Related to #28 

What it changes:

1. Adds autoconfiguration for Symfony 4 (when default `src/Kernel.php` is detected); Improves DX.

So when project bein compliant with new Symfony 4 structure, the configuration will look like:
```
            extensions:
                FriendsOfBehat\SymfonyExtension: ~
```
The standard `.env` file is loaded by default.

2. Explicit settings of `env` and `debug` parameters will override the configuration loaded from `.env` file. This also improves DX, as there is no need for hacky workarounds to get behat working.

3. Fixed issue with the badly named configuration `$config['kernel']['kernel'] = $debugMode;` in 1a39d31f5e

4. Tweaked README to include new configuration details


When it might seem only like an improvement, it's required to get behat working on new Symfony 4 structure when you: do not want to store a separate copy of `.env` with only `APP_ENV` changed to `test`, do not want to change those settings back and forth in `.env` file; which I consider not good practice and not good DX for sure 😄 

Commits
-------

09c6530115 Add autoconfiguration for Symfony 4, allow overriding .env setting by using explicit env and debug settings, tweaks
141074a24e Tweak README to include new configuration details for Symfony 4, tweak variable name
149d41087f Add symfony 4 configuration loading when bootstrap is null; tweaked formatting, naming, tests and readme
49c4fc2a0e Tweak README typos
99db863175 Tweaks after CR: remove obsolete docblocks, fix code formatting to match ECS
This commit is contained in:
Kamil Kokot
2018-12-19 15:08:16 +01:00
committed by GitHub
3 changed files with 209 additions and 96 deletions

View File

@@ -28,30 +28,53 @@ ensures that application behaviour will not be affected by stateful services.
FriendsOfBehat\SymfonyExtension: ~ FriendsOfBehat\SymfonyExtension: ~
``` ```
**Symfony 3 configuration** 3. Good luck & have fun!
```
FriendsOfBehat\SymfonyExtension: ## Configuration
SymfonyExtension provides kind of autoconfiguration feature.
When none explicit configuration is set, we will set for you sane default that will get you running fast.
**Default Symfony 3 configuration**
```
FriendsOfBehat\SymfonyExtension:
kernel: kernel:
bootstrap: 'var/bootstrap.php.cache' bootstrap: 'app/autoload.php' # you may want to use var/bootstrap.php.cache instead
path: app/AppKernel.php path: app/AppKernel.php
class: 'AppKernel' class: 'AppKernel'
env: test env: test
debug: true debug: true
``` ```
**Symfony 4 configuration** **Default Symfony 4 configuration**
``` ```
FriendsOfBehat\SymfonyExtension: FriendsOfBehat\SymfonyExtension:
# .env.dist file will be used if .env file does not exist # .env.dist file will be used if .env file does not exist
env_file: .env env_file: .env
kernel: kernel:
class: 'MyTrip\Kernel' bootstrap: ~
path: src/Kernel.php path: src/Kernel.php
debug: true class: 'App\Kernel'
``` env: test # When explicitly set, will override APP_ENV loaded from env_file file
debug: true # When explicitly set, will override APP_DEBUG loaded from env_file file
```
Symfony 4 does not have bootstrap file anymore and the environment is configured in the .env file. Symfony 4 is automatically detected, based on the existence of default `src/Kernel.php` kernel file.
3. Good luck & have fun! If you did not migrate to new Symfony structure yet or you are using custom paths/naming; you need to configure `kernel.bootstrap` parameter, to enable default Symfony 4 configuration as shown in the example below:
```
FriendsOfBehat\SymfonyExtension:
# env_file: .env # loaded from the default configuration
kernel:
bootstrap: ~ # this enables default Symfony 4 configuration
path: app/AppKernel.php
# class: 'App\Kernel' # loaded from the default configuration
# env: test # loaded from the default configuration
# debug: true # loaded from the default configuration
```
Of course, you can always change each of those settings.

View File

@@ -3,14 +3,17 @@ Feature: Not crashing Behat
As a Behat User As a Behat User
I want to have Behat up and running after enabling this extension I want to have Behat up and running after enabling this extension
Scenario: Not crashing Behat Scenario: Successful boot the Symfony kernel with autoconfiguration
Given a Behat configuration containing: Given a Behat configuration containing:
""" """
default: default:
extensions: extensions:
FriendsOfBehat\SymfonyExtension: FriendsOfBehat\SymfonyExtension: ~
kernel: """
bootstrap: ~ And a file "app/autoload.php" containing:
"""
<?php
""" """
And a file "app/AppKernel.php" containing: And a file "app/AppKernel.php" containing:
""" """
@@ -29,25 +32,32 @@ Feature: Not crashing Behat
When I run Behat When I run Behat
Then it should pass Then it should pass
Scenario: Not crashing Behat with CrossContainerExtension Scenario: Successful boot the Symfony kernel with explicit configuration
Given a Behat configuration containing: Given a Behat configuration containing:
""" """
default: default:
extensions: extensions:
FriendsOfBehat\SymfonyExtension: FriendsOfBehat\SymfonyExtension:
kernel: kernel:
bootstrap: ~ bootstrap: app/autoload.php
path: app/MyKernel.php
FriendsOfBehat\CrossContainerExtension: ~ class: MyKernel
env: test
debug: true
""" """
And a file "app/AppKernel.php" containing: And a file "app/autoload.php" containing:
"""
<?php
"""
And a file "app/MyKernel.php" containing:
""" """
<?php <?php
use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel class MyKernel extends Kernel
{ {
public function registerBundles() { return []; } public function registerBundles() { return []; }
public function registerContainerConfiguration(LoaderInterface $loader) {} public function registerContainerConfiguration(LoaderInterface $loader) {}
@@ -57,7 +67,38 @@ Feature: Not crashing Behat
When I run Behat When I run Behat
Then it should pass Then it should pass
Scenario: This extension boot a Symfony4 kernel
Scenario: Successful boot the Symfony 4 kernel with autoconfiguration
Given a Behat configuration containing:
"""
default:
extensions:
FriendsOfBehat\SymfonyExtension: ~
"""
And a file ".env" containing:
"""
APP_ENV=dev
"""
And a file "src/Kernel.php" containing:
"""
<?php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class Kernel extends BaseKernel
{
public function registerBundles() { return []; }
public function registerContainerConfiguration(LoaderInterface $loader) {}
}
"""
And a feature file with passing scenario
When I run Behat
Then it should pass
Scenario: Successful boot the Symfony 4 kernel with explicit configuration
Given a Behat configuration containing: Given a Behat configuration containing:
""" """
default: default:
@@ -98,9 +139,11 @@ Feature: Not crashing Behat
FriendsOfBehat\SymfonyExtension: FriendsOfBehat\SymfonyExtension:
env_file: .env_in_memory env_file: .env_in_memory
kernel: kernel:
bootstrap: ~
path: src/MyKernel.php path: src/MyKernel.php
class: MyKernel class: MyKernel
bootstrap: ~ env: dev
debug: true
""" """
And a file ".env_in_memory.dist" containing: And a file ".env_in_memory.dist" containing:
""" """
@@ -122,3 +165,33 @@ Feature: Not crashing Behat
And a feature file with passing scenario And a feature file with passing scenario
When I run Behat When I run Behat
Then it should pass Then it should pass
Scenario: Not crashing Behat with CrossContainerExtension
Given a Behat configuration containing:
"""
default:
extensions:
FriendsOfBehat\SymfonyExtension: ~
FriendsOfBehat\CrossContainerExtension: ~
"""
And a file "app/autoload.php" containing:
"""
<?php
"""
And a file "app/AppKernel.php" containing:
"""
<?php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel
{
public function registerBundles() { return []; }
public function registerContainerConfiguration(LoaderInterface $loader) {}
}
"""
And a feature file with passing scenario
When I run Behat
Then it should pass

View File

@@ -61,7 +61,35 @@ final class SymfonyExtension implements Extension
/** /**
* Enable or disable the debug mode * Enable or disable the debug mode
*/ */
private const DEBUG_MODE = false; private const DEFAULT_DEBUG_MODE = true;
/**
* Default Symfony configuration
*/
private const SYMFONY_DEFAULTS = [
'env_file' => null,
'kernel' => [
'bootstrap' => 'app/autoload.php',
'path' => 'app/AppKernel.php',
'class' => 'AppKernel',
'env' => self::DEFAULT_ENV,
'debug' => self::DEFAULT_DEBUG_MODE,
],
];
/**
* Default Symfony 4 configuration
*/
private const SYMFONY_4_DEFAULTS = [
'env_file' => '.env',
'kernel' => [
'bootstrap' => null,
'path' => 'src/Kernel.php',
'class' => 'App\Kernel',
'env' => self::DEFAULT_ENV,
'debug' => self::DEFAULT_DEBUG_MODE,
],
];
/** /**
* @var CrossContainerProcessor|null * @var CrossContainerProcessor|null
@@ -91,18 +119,16 @@ final class SymfonyExtension implements Extension
public function configure(ArrayNodeDefinition $builder): void public function configure(ArrayNodeDefinition $builder): void
{ {
$builder $builder
->addDefaultsIfNotSet()
->children() ->children()
->scalarNode('env_file')->defaultNull()->end() ->scalarNode('env_file')->end()
->arrayNode('kernel') ->arrayNode('kernel')
->addDefaultsIfNotSet() ->addDefaultsIfNotSet()
->children() ->children()
->scalarNode('bootstrap')->defaultValue('app/autoload.php')->end() ->scalarNode('bootstrap')->defaultFalse()->end()
->scalarNode('path')->defaultValue('app/AppKernel.php')->end() ->scalarNode('path')->end()
->scalarNode('class')->defaultValue('AppKernel')->end() ->scalarNode('class')->end()
->scalarNode('env')->defaultValue('test')->end() ->scalarNode('env')->end()
->booleanNode('debug')->defaultTrue()->end() ->booleanNode('debug')->end()
->end()
->end() ->end()
->end() ->end()
->end() ->end()
@@ -114,17 +140,7 @@ final class SymfonyExtension implements Extension
*/ */
public function load(ContainerBuilder $container, array $config): void public function load(ContainerBuilder $container, array $config): void
{ {
if (null !== $config['env_file']) { $config = $this->autoconfigure($container, $config);
$envFilePath = sprintf('%s/%s', $container->getParameter('paths.base'), $config['env_file']);
$envFilePath = file_exists($envFilePath) ? $envFilePath : $envFilePath.'.dist';
(new Dotenv())->load($envFilePath);
$environment = false !== getenv('APP_ENV') ? getenv('APP_ENV') : self::DEFAULT_ENV;
$debugMode = false !== getenv('APP_DEBUG') ? getenv('APP_DEBUG') : self::DEBUG_MODE;
$config['kernel']['env'] = $environment;
$config['kernel']['kernel'] = $debugMode;
}
$this->loadKernel($container, $config['kernel']); $this->loadKernel($container, $config['kernel']);
$this->loadKernelContainer($container); $this->loadKernelContainer($container);
@@ -145,9 +161,41 @@ final class SymfonyExtension implements Extension
{ {
} }
/** private function autoconfigure(ContainerBuilder $container, array $userConfig): array
* @param ContainerBuilder $container {
*/ $defaults = self::SYMFONY_DEFAULTS;
$symfonyFourKernelPath = sprintf('%s/%s', $container->getParameter('paths.base'), self::SYMFONY_4_DEFAULTS['kernel']['path']);
if ($userConfig['kernel']['bootstrap'] === null || file_exists($symfonyFourKernelPath)) {
$defaults = self::SYMFONY_4_DEFAULTS;
}
$userConfig['kernel']['bootstrap'] = $userConfig['kernel']['bootstrap'] === false ? null : $userConfig['kernel']['bootstrap'];
$config = array_replace_recursive($defaults, $userConfig);
if (null !== $config['env_file']) {
$this->loadEnvVars($container, $config['env_file']);
if (!isset($userConfig['kernel']['env']) && false !== getenv('APP_ENV')) {
$config['kernel']['env'] = getenv('APP_ENV');
}
if (!isset($userConfig['kernel']['debug']) && false !== getenv('APP_DEBUG')) {
$config['kernel']['debug'] = getenv('APP_DEBUG');
}
}
return $config;
}
private function loadEnvVars(ContainerBuilder $container, string $fileName): void
{
$envFilePath = sprintf('%s/%s', $container->getParameter('paths.base'), $fileName);
$envFilePath = file_exists($envFilePath) ? $envFilePath : $envFilePath . '.dist';
(new Dotenv())->load($envFilePath);
}
private function loadKernel(ContainerBuilder $container, array $config): void private function loadKernel(ContainerBuilder $container, array $config): void
{ {
$definition = new Definition($config['class'], [ $definition = new Definition($config['class'], [
@@ -167,9 +215,6 @@ final class SymfonyExtension implements Extension
$this->requireKernelBootstrapFile($container->getParameter('paths.base'), $config['bootstrap']); $this->requireKernelBootstrapFile($container->getParameter('paths.base'), $config['bootstrap']);
} }
/**
* @param ContainerBuilder $container
*/
private function loadKernelContainer(ContainerBuilder $container): void private function loadKernelContainer(ContainerBuilder $container): void
{ {
$containerDefinition = new Definition(Container::class); $containerDefinition = new Definition(Container::class);
@@ -181,25 +226,16 @@ final class SymfonyExtension implements Extension
$container->setDefinition(self::KERNEL_CONTAINER_ID, $containerDefinition); $container->setDefinition(self::KERNEL_CONTAINER_ID, $containerDefinition);
} }
/**
* @param ContainerBuilder $container
*/
private function loadDriverKernel(ContainerBuilder $container): void private function loadDriverKernel(ContainerBuilder $container): void
{ {
$container->setDefinition(self::DRIVER_KERNEL_ID, $container->findDefinition(self::KERNEL_ID)); $container->setDefinition(self::DRIVER_KERNEL_ID, $container->findDefinition(self::KERNEL_ID));
} }
/**
* @param ContainerBuilder $container
*/
private function loadSharedKernel(ContainerBuilder $container): void private function loadSharedKernel(ContainerBuilder $container): void
{ {
$container->setDefinition(self::SHARED_KERNEL_ID, $container->findDefinition(self::KERNEL_ID)); $container->setDefinition(self::SHARED_KERNEL_ID, $container->findDefinition(self::KERNEL_ID));
} }
/**
* @param ContainerBuilder $container
*/
private function loadSharedKernelContainer(ContainerBuilder $container): void private function loadSharedKernelContainer(ContainerBuilder $container): void
{ {
$containerDefinition = new Definition(Container::class); $containerDefinition = new Definition(Container::class);
@@ -212,8 +248,6 @@ final class SymfonyExtension implements Extension
} }
/** /**
* @param ContainerBuilder $container
*
* @throws \Exception * @throws \Exception
*/ */
private function loadKernelRebooter(ContainerBuilder $container): void private function loadKernelRebooter(ContainerBuilder $container): void
@@ -225,8 +259,6 @@ final class SymfonyExtension implements Extension
} }
/** /**
* @param ContainerBuilder $container
*
* @throws \Exception * @throws \Exception
*/ */
private function declareSymfonyContainers(ContainerBuilder $container): void private function declareSymfonyContainers(ContainerBuilder $container): void
@@ -257,9 +289,6 @@ final class SymfonyExtension implements Extension
} }
} }
/**
* @param ExtensionManager $extensionManager
*/
private function initializeCrossContainerProcessor(ExtensionManager $extensionManager): void private function initializeCrossContainerProcessor(ExtensionManager $extensionManager): void
{ {
/** @var CrossContainerExtension $extension */ /** @var CrossContainerExtension $extension */
@@ -269,9 +298,6 @@ final class SymfonyExtension implements Extension
} }
} }
/**
* @param ExtensionManager $extensionManager
*/
private function registerSymfonyDriverFactory(ExtensionManager $extensionManager): void private function registerSymfonyDriverFactory(ExtensionManager $extensionManager): void
{ {
/** @var MinkExtension|null $minkExtension */ /** @var MinkExtension|null $minkExtension */
@@ -286,12 +312,6 @@ final class SymfonyExtension implements Extension
)); ));
} }
/**
* @param string $basePath
* @param string $kernelPath
*
* @return string|null
*/
private function getKernelFile(string $basePath, string $kernelPath): ?string private function getKernelFile(string $basePath, string $kernelPath): ?string
{ {
$possibleFiles = [ $possibleFiles = [
@@ -309,9 +329,6 @@ final class SymfonyExtension implements Extension
} }
/** /**
* @param string $basePath
* @param string|null $bootstrapPath
*
* @throws \DomainException * @throws \DomainException
*/ */
private function requireKernelBootstrapFile(string $basePath, ?string $bootstrapPath): void private function requireKernelBootstrapFile(string $basePath, ?string $bootstrapPath): void