Visitar URL original
Add first-class PHP configuration format by TavoNiievez · Pull Request #6946 · Codeception/Codeception · GitHub
Skip to content

Add first-class PHP configuration format - #6946

Open
TavoNiievez wants to merge 1 commit into
mainfrom
php_config
Open

TavoNiievez wants to merge 1 commit into
mainfrom
php_config

Conversation

@TavoNiievez

Copy link
Copy Markdown
Member

Summary

Adds a PHP configuration format as a first-class alternative to YAML, designed to become the canonical way to configure Codeception in future versions. A codeception.php (or *.suite.php / env file) returns a fluent builder — or a raw array — instead of YAML. This unlocks IDE autocomplete, PHPStan-checkable config, ::class constants, native getenv(), and real PHP expressions (E_ALL & ~E_DEPRECATED), while keeping a 1:1 mapping to every existing YAML key. YAML is untouched and remains the default — the PHP format is fully opt-in.

<?php
use Codeception\Config\GlobalConfig;
use Codeception\Config\SuiteConfig;

return GlobalConfig::create()
    ->namespace('App\Tests')
    ->paths(tests: 'tests', output: 'tests/_output', data: 'tests/_data', support: 'tests/Support')
    ->settings(colors: true, memoryLimit: '1024M')
    ->suite('Functional', SuiteConfig::create()
        ->actor('FunctionalTester')
        ->module('Asserts')
        ->module('Db', ['dsn' => getenv('DB_DSN')]));

What's new

Builder API (src/Codeception/Config/, namespace Codeception\Config\):

  • GlobalConfig, SuiteConfig (both final), ConfigInterface (forever-contract = toArray() only), AbstractConfigBuilder (@internal), Params.
  • Uniform pattern: singular appenders (module(), moduleConfig(), extension(), suite(), env(), commands()) + one raw escape hatch merge(). settings() uses explicit named nullable params (camelCase → snake_case) for autocomplete.

Loader (Configuration.php): per role (global / suite / env), if any .php variant exists, PHP wins and YAML is ignored — one rule everywhere. Config files are loaded exactly once, inside an isolated closure wrapped in output buffering (stray echo can't corrupt --xml/--json output), with concrete-type guards (a SuiteConfig returned in global position throws a clear error naming the file and expected type). Cross-format extends works both directions.

Params: %param% stays YAML-only; PHP configs use getenv() in the global file and Params::get() in suite/env files (with a self-explaining exception if Params::get() is called too early during global load).

Tooling & scaffolding:

  • config:validate now reports the loaded config file path.
  • config:to-php — new command migrating existing YAML projects to builder-style PHP (--dry-run prints without writing; renderer Lib/Generator/PhpConfigFile.php).
  • bootstrap, init, g:suite, g:env gain PHP output; g:suite/g:env detect the project format and no longer risk generating a silently-shadowed file when the other extension already exists.

Design notes

  • New Codeception\Config\ namespace deliberately avoids colliding with the existing Codeception\Configuration class.
  • A reflection drift-guard test fails if any core $defaultConfig / $defaultSuiteSettings key gains no builder method — the canonical API can't silently lag core.
  • Mutable builders (matching Rector / PHP-CS-Fixer); merge() renamed from with() since with* implies immutable clone in PHP.
  • Considered and rejected: closure-style config, attributes, immutable builders, a separate lib-config package, var_export array generation. (Rationale available on request.)

Backward compatibility

Non-breaking and additive. YAML behavior is unchanged; YAML remains the default scaffold. Flipping the default to PHP, and any future YAML deprecation, is left as a maintainer decision — not smuggled in here.

Tests

  • 30 unit tests (tests/unit/Codeception/Config/): every builder method → expected key/value, snake_case mapping, guards, merge() order, drift-guards, Params::get() timing.
  • 13 cli tests (tests/cli/ConfigPhpFormatCest.php): discovery, php-wins, env-wins, cross-format extends, type/echo/invalid-return errors, inline-suite shadowing, config:to-php migrate → build → run.
  • Verified against a CI-faithful Linux run (build + web server + run cli,unit,coverage), plus phpstan analyse src and composer cs-tests clean.

Unrelated changes (disclosed)

This commit also bumps CI actions: actions/checkout@v6 → v7 and actions/cache@v3 → v6 in .github/workflows/build.yml. Not history-rewritten; called out here for review.

This comment was marked as resolved.

@TavoNiievez
TavoNiievez force-pushed the php_config branch 2 times, most recently from 8ddfeaf to 17c7e25 Compare July 7, 2026 23:42
- Introduced support for PHP configuration files alongside YAML.
- Updated Api, Unit, and Bootstrap templates to generate PHP config files.
- Added methods to handle PHP config generation and validation.
- Enhanced test cases to validate the new PHP configuration format.
- Migrated existing YAML configurations to PHP format in tests.
- Improved error handling and output for configuration validation.
- Added new test cases to ensure compatibility and correctness of the new configuration system.
@TavoNiievez

Copy link
Copy Markdown
Member Author

I added this example PR to demonstrate the functionality and the new API: Codeception/symfony-module-tests#66

@TavoNiievez
TavoNiievez requested review from SamMousa and W0rma July 8, 2026 02:20
@TavoNiievez

Copy link
Copy Markdown
Member Author

cc. @ThomasLandauer , @burned42 & @samsonasik

@ThomasLandauer

Copy link
Copy Markdown
Member

Moving away from YAML is an excellent idea IMO!

Symfony just moved away from PHP methods to PHP arrays (e.g. ['namespace' => 'App\Tests'] instead of ->namespace('App\Tests')). I'm sure you know that. Do you know the reason why?

@ThomasLandauer

Copy link
Copy Markdown
Member

Detail while looking at Codeception/symfony-module-tests#66 (comment)
Would it be possible to pass the modules as FQCN too? So something like ->module(Asserts::class) instead of ->module('Asserts')?

@W0rma

W0rma commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Symfony just moved away from PHP methods to PHP arrays (e.g. ['namespace' => 'App\Tests'] instead of ->namespace('App\Tests')). I'm sure you know that. Do you know the reason why?

The reasons are explained in symfony/symfony#62092

@TavoNiievez

Copy link
Copy Markdown
Member Author

Hi guys, thanks for the reply.

I was fully aware of the information shared by @W0rma before and during my work on this PR.
In short, in Symfony it was very difficult to know whether the next element in the fluid interface should be another method, a parameter, or an array. That’s because the configurations in Symfony are quite a bit more complex than in Codeception.
In our case, we don’t have to deal with complex auto-generated objects, so this approach works well for us.

@ThomasLandauer Yes, I also thought about integrating that… But I preferred to keep this PR solely as a 1-to-1 mapping of the current Codeception configuration: if it’s a string, we handle strings; if it’s classes, we handle classes. It can easily be added in a later PR.

@ThomasLandauer

Copy link
Copy Markdown
Member

Symfony: Yeah, that certainly makes sense :-)

Other changes ("while you're at it..."): Your point is certainly reasonable. What I wanted so say: I guess that most people will update their config manually (even if there would be automatic tools, the config is so short that it's probably not worth installing one...). So if somebody needs to sit down and rewrite everything, it's certainly better to make all upcoming changes at once. So I would say: If you encounter something strange, just change it right away :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants