8000 [FrameworkBundle] Generate `Config` class by alexandre-daubois · Pull Request #58771 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[FrameworkBundle] Generate Config class #58771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
from

Conversation

alexandre-daubois
Copy link
Member
@alexandre-daubois alexandre-daubois commented Nov 5, 2024
Q A
Branch? 7.3
Bug fix? no
New feature? yes
Deprecations? no
Issues -
8000 License MIT

This PR adds a new Config class to configure a Symfony application:

<?php

/** @var \Symfony\Config\Config $config */
$config->framework([
    'secret' => 'abc123',
]);

$config->parameters([
    'some_parameter' => 'some_value',
]);

$config
    ->imports(['test.php'])
    ->services([
        ['id' => 'some.service', 'class' => \App\Service\ConcreteService::class],
    ]);

One method exists for each extension, as well as services(), imports() and parameters().

The class is generated at cache warmup, allowing to generate the corresponding phpdoc. Here a sneak peek of what the functions looks like:

<?php

namespace Symfony\Config;

/**
 * This trait is automatically generated to help in creating a config.
 */
trait WebProfilerTrait
{
    /**
     * @param array{
     *     toolbar?: array{ // Profiler toolbar configuration
     *         enabled?: bool, // Default: false
     *         ajax_replace?: bool, // Replace toolbar on AJAX requests Default: false
     *     },
     *     intercept_redirects?: bool, // Default: false
     *     excluded_ajax_paths?: string|int|float|bool, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt"
     * } $config
     */
    public function webProfiler(array $config): static
    {
        $this->webProfilerConfig->configure($config);

        return $this;
    }
}

This will bring nice IDE autocomplete as well as strong static analysis. Also, you're always one click away from the complete documentation.


Credits to Nicolas, Kevin and Ryan for the idea 🙂

@carsonbot

This comment was marked as outdated.

@alexandre-daubois
Copy link
Member Author

Thank you for your review! I'm having a look at it and will soon address your comments

@fabpot fabpot modified the milestones: 7.2, 7.3 Nov 20, 2024
@alexandre-daubois
Copy link
Member Author
alexandre-daubois commented Nov 24, 2024

Prototyped arrays are now supported, here's a screenshot from the newly generated config (snippet):

/* @param array{
 *     access_denied_url?: string|int|float|bool,
 *     session_fixation_strategy?: "none"|"migrate"|"invalidate",
 *     hide_user_not_found?: bool,
 *     erase_credentials?: bool,
 *     access_decision_manager?: array{
 *         strategy?: "affirmative"|"consensus"|"unanimous"|"priority",
 *         service?: string|int|float|bool,
 *         strategy_service?: string|int|float|bool,
 *         allow_if_all_abstain?: bool,
 *         allow_if_equal_granted_denied?: bool,
 *     },
 *     password_hashers?: array<array{
 *         algorithm?: string|int|float|bool,
 *         migrate_from?: array<string|int|float|bool>,
 *         hash_algorithm?: string|int|float|bool,
 *         key_length?: string|int|float|bool,
 *         ignore_case?: bool,
 *         encode_as_base64?: bool,
 *         iterations?: string|int|float|bool,
 *         cost?: int<4, 31>,
 *         memory_cost?: string|int|float|bool,
 *         time_cost?: string|int|float|bool,
 *         id?: string|int|float|bool,
 *     }>,
 *     providers?: array<array{
 *         id?: string|int|float|bool,
 *         chain?: array{
 *             providers?: array<string|int|float|bool>,
 *         },
 *         memory?: array{
 *             users?: array<array{
 *                 password?: string|int|float|bool,
 *                 roles?: array<string|int|float|bool>,
 *             }>,
 *         },
 *         ldap?: array{
 *             service: string|int|float|bool,
 *             base_dn: string|int|float|bool,
 *             search_dn?: string|int|float|bool,
 *             search_password?: string|int|float|bool,
 *             extra_fields?: array<string|int|float|bool>,
 *             default_roles?: array<string|int|float|bool>,
 *             uid_key?: string|int|float|bool,
 *             filter?: string|int|float|bool,
 *             password_attribute?: string|int|float|bool,
 *         },
 *     }>,
 *     ...
 */

Providers, password hashers, etc. are all prototypes.

@nicolas-grekas
Copy link
Member

To anyone willing to see improved support for this updated PHP config format, please upvote this phpstorm issue ;)
https://youtrack.jetbrains.com/issue/WI-79484/ArrayShape-and-PHPDoc-shapes-on-param-doesnt-hint-well

Copy link
Member
@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing pre-filled shapes for "services", "imports" and "parameters" tree, which are not defined by a bundle, so don't have a configuration definition.

Also, can you investigate how an IDE like vscode could add support for auto-completing shapes? who's in charge of the most used vscode extensions so that we can ping them?

@nicolas-grekas
Copy link
Member

I'm also wondering if using named arguments for the 1st level provides the best DX: the IDE cannot know we want to type the name of an argument quickly enough I fear. Whereas if we start typing [', the IDE has a very precise context now.

@alexandre-daubois
Copy link
Member Author

I like the named argument way of doing because it is a bit more convenient to Cmd/Ctrl+Click on the named argument to have the full doc at a glance, whereas the Cmd/Ctrl+Click isn't available for array indexes. If there are a lot of bundles enabled, being able to go to the "definition" of the named argument could really help

@ondrejmirtes
Copy link
Contributor

You can now enjoy comments in PHPDoc types as part of PHPStan 2.1.6 and phpstan/phpdoc-parser 2.1.0 🥳

https://phpc.social/@OndrejMirtes/114031365837991072

@alexandre-daubois
Copy link
Member Author

Great news, thank you @ondrejmirtes for being that responsive on this request!

@alexandre-daubois
Copy link
Member Author

Comments in array shapes are live in the latest PhpStorm 2025.1 Beta:

image

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Mar 24, 2025

I'm shaking again this config format to find the best syntax. I'm wondering about how one could define several arrays in the same file. We do so currently when some settings depend on the env - with #[WhenEnv]. Porting these to the new format is going to be not fun at the moment.

What about using a config object with as many methods as needed, and no return value?

<?php

/** @var \Symfony\Config\Config $config */;

$config->framework([...]);

if ('dev' === $config->env) {
    $config->framework([...]);
}

If we want to keep splitting shapes in separate files (to ease scrolling the shape?) we might generate traits and use them in the generated Config class.

WDYT?

@alexandre-daubois
Copy link
Member Author

Suits me fine, I particularly like the trait part. I definitely think it would be way easier to scroll through shapes if we have FrameworkConfigTrait, WebProfilerConfigTrait, etc.

What about using a config object with as many methods as needed, and no return value

I agree, just a suggestion on the last bit. What about actually returning $this to chain this way?

$config
    ->framework([...])
    ->webProfiler([...])
    ->security([...]);

fabpot added a commit that referenced this pull request Apr 4, 2025
…bois)

This PR was merged into the 7.3 branch.

Discussion
----------

[Config] Add `NodeDefinition::docUrl()`

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

Adding such information would allow extensions and bundles to provide even more info with a documentation "one click away".

The primary goal is to use this feature in conjunction with #58771, allowing to dump a ``@see` https://symfony.com/doc/...` right next to the configuration array shape.

Commits
-------

3c7fce2 [Config] Add `NodeDefinition::docUrl()`
symfony-splitter pushed a commit to symfony/security-bundle that referenced this pull request Apr 4, 2025
…bois)

This PR was merged into the 7.3 branch.

Discussion
----------

[Config] Add `NodeDefinition::docUrl()`

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

Adding such information would allow extensions and bundles to provide even more info with a documentation "one click away".

The primary goal is to use this feature in conjunction with symfony/symfony#58771, allowing to dump a ``@see` https://symfony.com/doc/...` right next to the configuration array shape.

Commits
-------

3c7fce2e326 [Config] Add `NodeDefinition::docUrl()`
symfony-splitter pushed a commit to symfony/framework-bundle that referenced this pull request Apr 4, 2025
…bois)

This PR was merged into the 7.3 branch.

Discussion
----------

[Config] Add `NodeDefinition::docUrl()`

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

Adding such information would allow extensions and bundles to provide even more info with a documentation "one click away".

The primary goal is to use this feature in conjunction with symfony/symfony#58771, allowing to dump a ``@see` https://symfony.com/doc/...` right next to the configuration array shape.

Commits
-------

3c7fce2e326 [Config] Add `NodeDefinition::docUrl()`
symfony-splitter pushed a commit to symfony/web-profiler-bundle that referenced this pull request Apr 4, 2025
…bois)

This PR was merged into the 7.3 branch.

Discussion
----------

[Config] Add `NodeDefinition::docUrl()`

| Q             | A
| ------------- | ---
| Branch?       | 7.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

Adding such information would allow extensions and bundles to provide even more info with a documentation "one click away".

The primary goal is to use this feature in conjunction with symfony/symfony#58771, allowing to dump a ``@see` https://symfony.com/doc/...` right next to the configuration array shape.

Commits
-------

3c7fce2e326 [Config] Add `NodeDefinition::docUrl()`
@nicolas-grekas
Copy link
Member
nicolas-grekas commented Apr 15, 2025

JetBrains did their part, thanks to them! Coming to PhpStorm 2025.1

#[ArrayShape] and PHPDoc shapes on param doesn't hint well : WI-79484

Allow comments in array shape : WI-73436

What about actually returning $this to chain this way?

Why not indeed.

Up to resume work on this topic?

@alexandre-daubois
Copy link
Member Author

It's a bit of a rush at the moment, but as soon as I have a bit of time to get on with the job, this PR is my priority. 🙂

@alexandre-daubois alexandre-daubois force-pushed the generate-array-shapes branch 4 times, most recently from 43b5e13 to e394d68 Compare April 18, 2025 13:35
@alexandre-daubois alexandre-daubois changed the title [FrameworkBundle] Generate configuration functions [FrameworkBundle] Generate Config class Apr 18, 2025
@alexandre-daubois
Copy link
Member Author

I just pushed the revamped version. PR description updated with the new syntax. After playing with, navigating through the shapes is much simpler now that traits are generated.


private static function generateInlinePhpDocForNode(BaseNode $node): string
{
$hasContent = false;
Copy link
Member
@nicolas-grekas nicolas-grekas Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needless var: $comment should start empty and be prefixed only at the end, if not empty anymore
also, CR/LF in $comment should be replaced by a space so that we're sure it remains a oneliner

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It currently outputs something like this for framework:

     *         strict_requirements?: string|int|float|bool, // set to true to throw an exception when a parameter does not match the requirements set to false to disable exceptions when a parameter does not match the requirements (and return null instead) set to null to disable parameter checks against requirements 'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production Default: true

Maybe we could instead replace \r and \n with ; ?

Copy link
Member Author
@alexandre-daubois alexandre-daubois Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually maybe not, it seems it's a particular case. Other places end the info with a ., so maybe we should adjust the info of this particular node.

// the closure forbids access to the private scope in the included file
$load = \Closure::bind(function ($path, $env) use ($container, $loader, $resource, $type) {
$config = class_exists(Config::class) ? new Config() : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the generated Config class have constructor arguments when those are never used?
Also, it'd be nice to add public readonly vars to the $config object to get access to the other tools one could use, especially $config->env (possibly $config->path, container, loader, resource, type, although I'm not sure what they could be used for there - for thoughts.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, it now looks like this:

<?php

namespace Symfony\Config;

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Config\FrameworkConfig;
use Symfony\Config\SecurityConfig;
use Symfony\Config\TwigConfig;
use Symfony\Config\WebProfilerConfig;
use Symfony\Config\MonologConfig;
use Symfony\Config\DebugConfig;

/**
 * This class is automatically generated to help in creating a config.
 */
class Config 
{
    use \Symfony\Component\DependencyInjection\Resource\ImportsTrait;
    use \Symfony\Component\DependencyInjection\Resource\ParametersTrait;
    use \Symfony\Component\DependencyInjection\Resource\ServicesTrait;
    use \Symfony\Config\FrameworkTrait;
    use \Symfony\Config\SecurityTrait;
    use \Symfony\Config\TwigTrait;
    use \Symfony\Config\WebProfilerTrait;
    use \Symfony\Config\MonologTrait;
    use \Symfony\Config\DebugTrait;
    private $builders;
    private $frameworkConfig;
    private $securityConfig;
    private $twigConfig;
    private $webProfilerConfig;
    private $monologConfig;
    private $debugConfig;
    public function __construct(public readonly ?string $env)
    {
        $this->frameworkConfig = new FrameworkConfig();
        $this->securityConfig = new SecurityConfig();
        $this->twigConfig = new TwigConfig();
        $this->webProfilerConfig = new WebProfilerConfig();
        $this->monologConfig = new MonologConfig();
        $this->debugConfig = new DebugConfig();
        $this->builders = [$this->frameworkConfig, $this->securityConfig, $this->twigConfig, $this->webProfilerConfig, $this->monologConfig, $this->debugConfig];
    }
    public function getBuilders(): array
    {
        return $this->builders;
    }

}

$env is injected. It's always possible to add other ones if needed, $env being the most important to me as well

private array $parameters = [];

/**
* @var array<string, mixed> $parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't mixed too broad?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Narrowed the type a bit to array<string, array|bool|string|int|float|\UnitEnum|null>

* @var array<array{
* id: string,
* class?: string|int|float|bool,
* }>|array<string, class-string|null> $services
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is minimalistic. In reality, we could be way more descriptive here, see what YamlFileLoader describes for services. Maybe you preferred postponing this to a follow up?

Copy link
Member Author
@alexandre-daubois alexandre-daubois Apr 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started implementing what's left (lazy, abstract, args, synthetic, etc.) but there's a bit more work than I expected. I propose to validate the PR as-is, then comeback to this bit when the rest is OK?

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

Successfully merging this pull request may close these issues.

0