MidТеория4 min

Конфигурация

DotEnv component, ExpressionLanguage, переменные окружения, файлы конфигурации, параметры

DotEnv Component

DotEnv загружает переменные окружения из .env файлов. Symfony автоматически использует его в dev и test окружениях.

Порядок загрузки .env файлов

1. .env                    # Base values, committed to VCS
2. .env.local              # Local overrides, NOT committed
3. .env.{env}              # Environment-specific (e.g., .env.test)
4. .env.{env}.local        # Local env-specific, NOT committed

Каждый следующий файл переопределяет значения из предыдущего. Реальные переменные окружения имеют наивысший приоритет.

# .env
APP_ENV=dev
APP_SECRET=change_me
DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=18"
MAILER_DSN=smtp://localhost:1025
# .env.local (not committed to VCS)
DATABASE_URL="postgresql://real_user:[email protected]:5432/myapp"
APP_SECRET=a_real_secret_value
# .env.test
KERNEL_CLASS='App\Kernel'
DATABASE_URL="postgresql://app:[email protected]:5432/app_test"
MAILER_DSN=null://null

Подвох экзамена: В production .env файлы НЕ используются. Переменные окружения устанавливаются через Docker, systemd или конфигурацию хостинга. Symfony проверяет наличие .env.local.php (скомпилированные переменные) или использует системные переменные.

Синтаксис .env

# Simple values
APP_DEBUG=true
APP_NAME=MyApp

# Quoted values (for spaces and special chars)
APP_TITLE="My Application"

# Referencing other variables
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_URL="postgresql://user:pass@${DATABASE_HOST}:${DATABASE_PORT}/db"

# Default values
REDIS_URL="${REDIS_URL:-redis://localhost:6379}"

# Multiline (use quotes)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

ExpressionLanguage Component

ExpressionLanguage позволяет компилировать и выполнять выражения. Используется в Security, Routing, Workflow и DI.

<?php

declare(strict_types=1);

use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

// Evaluate expression
$result = $expressionLanguage->evaluate(
    'product.price * quantity * (1 - discount / 100)',
    [
        'product' => (object)['price' => 100],
        'quantity' => 3,
        'discount' => 10,
    ]
);
// $result = 270.0

// Compile to PHP
$phpCode = $expressionLanguage->compile(
    'user.isActive() and user.age >= 18',
    ['user']
);
// $phpCode = "$user->isActive() && $user->getAge() >= 18"

ExpressionLanguage в конфигурации

# config/services.yaml
services:
    App\Service\ApiClient:
        arguments:
            $timeout: "@=parameter('app.default_timeout') * 2"
            $baseUrl: "@=service('router').generate('api_base')"
<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\Attribute\Autowire;

final class ApiClient
{
    public function __construct(
        #[Autowire(expression: 'parameter("app.default_timeout") * 2')]
        private readonly int $timeout,
    ) {
    }
}

Файлы конфигурации

YAML конфигурация

# config/packages/framework.yaml
framework:
    secret: '%env(APP_SECRET)%'
    csrf_protection: true
    http_method_override: false
    handle_all_throwables: true

    session:
        handler_id: null
        cookie_secure: auto
        cookie_samesite: lax
        storage_factory_id: session.storage.factory.native

    php_errors:
        log: true

PHP конфигурация

<?php

declare(strict_types=1);

// config/packages/framework.php
use Symfony\Config\FrameworkConfig;

return static function (FrameworkConfig $framework): void {
    $framework->secret('%env(APP_SECRET)%');
    $framework->csrfProtection()->enabled(true);
    $framework->httpMethodOverride(false);

    $framework->session()
        ->handlerId(null)
        ->cookieSecure('auto')
        ->cookieSamesite('lax');
};

Symfony 8.0: Конфигурация через XML и аннотации удалена из экзамена. Только YAML и PHP-атрибуты.

Environment-specific конфигурация

# config/packages/framework.yaml

# Base config for all environments
framework:
    secret: '%env(APP_SECRET)%'

# Override for dev only
when@dev:
    framework:
        profiler:
            collect: true

# Override for test
when@test:
    framework:
        test: true
        session:
            storage_factory_id: session.storage.factory.mock_file

# Override for prod
when@prod:
    framework:
        router:
            strict_requirements: null

Параметры

# config/services.yaml
parameters:
    # Scalar parameters
    app.admin_email: '[email protected]'
    app.items_per_page: 20

    # Array parameter
    app.supported_locales: ['en', 'fr', 'de']

    # Environment variable processors
    app.feature_flags: '%env(json:FEATURE_FLAGS)%'
    app.debug_emails: '%env(csv:DEBUG_EMAILS)%'
    app.cache_ttl: '%env(int:CACHE_TTL)%'
    app.use_ssl: '%env(bool:USE_SSL)%'

    # Nested reference
    app.upload_dir: '%kernel.project_dir%/var/uploads'

    # Constant
    app.max_connections: !php/const App\Config::MAX_CONNECTIONS

Доступ к параметрам

<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

final class ConfigController extends AbstractController
{
    public function __construct(
        #[Autowire('%app.admin_email%')]
        private readonly string $adminEmail,

        #[Autowire('%app.items_per_page%')]
        private readonly int $itemsPerPage,

        // Full parameter bag
        private readonly ParameterBagInterface $params,
    ) {
    }

    public function index(): Response
    {
        // Via parameter bag
        $locales = $this->params->get('app.supported_locales');
        $projectDir = $this->params->get('kernel.project_dir');

        // Via AbstractController
        $secret = $this->getParameter('kernel.secret');

        return new Response("Admin: {$this->adminEmail}");
    }
}

Configuration Tree (Bundle Configuration)

<?php

declare(strict_types=1);

namespace App\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

final class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder(): TreeBuilder
    {
        $treeBuilder = new TreeBuilder('app');

        $treeBuilder->getRootNode()
            ->children()
                ->scalarNode('api_key')->isRequired()->end()
                ->integerNode('timeout')
                    ->defaultValue(30)
                    ->min(1)->max(300)
                ->end()
                ->enumNode('mode')
                    ->values(['sync', 'async', 'batch'])
                    ->defaultValue('async')
                ->end()
                ->booleanNode('debug')->defaultFalse()->end()
                ->arrayNode('providers')
                    ->arrayPrototype()
                        ->children()
                            ->scalarNode('name')->isRequired()->end()
                            ->scalarNode('dsn')->isRequired()->end()
                            ->integerNode('priority')->defaultValue(0)->end()
                        ->end()
                    ->end()
                ->end()
            ->end();

        return $treeBuilder;
    }
}

Отладка конфигурации

# Show current config for a bundle
php bin/console debug:config framework

# Show full reference config
php bin/console config:dump-reference framework

# Show all parameters
php bin/console debug:container --parameters

Итоги

  • DotEnv загружает переменные из .env файлов (не в production)
  • Порядок: .env -> .env.local -> .env.{env} -> .env.{env}.local
  • ExpressionLanguage -- выражения в конфигурации и Security
  • when@dev, when@test, when@prod -- конфигурация по окружению
  • Параметры доступны через #[Autowire('%param%')] или ParameterBagInterface
  • debug:config, config:dump-reference -- отладка конфигурации