The Wayback Machine - https://web.archive.org/web/20250219162248/https://github.com/symfony/symfony/pull/58771
Skip to content
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

[FrameworkBundle] Generate configuration functions #58771

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

Conversation

alexandre-daubois
Copy link
Contributor

@alexandre-daubois alexandre-daubois commented Nov 5, 2024

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

This PR adds a new config() function to configure a Symfony application:

<?php

use function Symfony\Config\framework;

return framework([
    'cache' => [
        'prefix_seed' => 'seed_'.$env.'_',
    ],
    'translator' => [
        'enabled' => true,
    ],
]);

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

The functions 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;

// ...

function maker(
    /** @var array{
     *     root_namespace?: string|int|float|bool, // Default: "App"
     *     generate_final_classes?: bool, // Default: true
     *     generate_final_entities?: bool, // Default: false
     * } $config */
    array $config = []): \Closure {
    return static function (\Symfony\Config\MakerConfig $makerConfig) use ($config) {
        $makerConfig->configure($config);
    };
}

function webProfiler(
    /** @var array{
     *     toolbar?: array{ // Profiler toolbar configuration 
     *         enabled?: bool, // Default: false
     *         ajax_replace?: bool, // Replace toolbar on AJAX requests Default: false
     *     }, // Profiler toolbar configuration 
     *     intercept_redirects?: bool, // Default: false
     *     excluded_ajax_paths?: string|int|float|bool, // Default: "^/((index|app(_[\\w]+)?)\\.php/)?_wdt"
     * } $config */
    array $config = []): \Closure {
    return static function (\Symfony\Config\WebProfilerConfig $webProfilerConfig) use ($config) {
        $webProfilerConfig->configure($config);
    };
}

// ...

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

⚠️ Due to a issue in PHPStorm, this PR requires the following Youtrack issue to be resolved to be working as expected: https://youtrack.jetbrains.com/issue/WI-79484/ArrayShape-and-PHPDoc-shapes-on-param-doesnt-hint-well


Credits to Nicolas, Kevin and Ryan for the idea 🙂

@carsonbot

This comment was marked as outdated.

@alexandre-daubois
Copy link
Contributor 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
Contributor 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

Comment on lines 87 to 88
if (class_exists(ConfigFunctionAwareBuilderGeneratorInterface::class) && $configurations) {
$generator->buildConfigFunction($configurations);
Copy link
Member

Choose a reason for hiding this comment

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

this might miss some tests... ;)

Suggested change
if (class_exists(ConfigFunctionAwareBuilderGeneratorInterface::class) && $configurations) {
$generator->buildConfigFunction($configurations);
if ($generator instanceof ConfigFunctionAwareBuilderGeneratorInterface && $configurations) {
$generator->buildConfigFunction($configurations)();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, tests added 👍

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
Contributor 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

@alexandre-daubois alexandre-daubois force-pushed the generate-array-shapes branch 3 times, most recently from 251a4bc to 2f6b0ba Compare February 12, 2025 13:38
@alexandre-daubois
Copy link
Contributor Author

PR updated with your suggestion @nicolas-grekas, you can find an example in the fixtures.

@stof
Copy link
Member

stof commented Feb 12, 2025

@nicolas-grekas I'm not sure @var works fine for function arguments (the only case where we use them right now is on promoted properties, where they work because @var works in properties)

@stof
Copy link
Member

stof commented Feb 12, 2025

  • For services()/import(), there’s no need to redefine them using nodes. A template with manually written PHPDoc will be much easier to maintain.

or even just a file directly in the package that gets required directly instead of copying a template to the file being generated (there would be nothing dynamic in the template here)

@alexandre-daubois
Copy link
Contributor Author

alexandre-daubois commented Feb 12, 2025

I'm not sure @var works fine for function arguments

I tried while doing the update, PhpStorm supports it fine

@nicolas-grekas
Copy link
Member

or even just a file directly in the package that gets required directly

💯 the builder would just generate the appropriate require of the file located in the vendor dir

@alexandre-daubois
Copy link
Contributor Author

alexandre-daubois commented Feb 12, 2025

The config.php file now has the following requires and the files are added to the package resources:

require_once dirname(__DIR__, 5).'/vendor/symfony/dependency-injection/Resource/imports.php';
require_once dirname(__DIR__, 5).'/vendor/symfony/dependency-injection/Resource/services.php';
require_once dirname(__DIR__, 5).'/vendor/symfony/dependency-injection/Resource/parameters.php';

I'm investigating on failing tests

@yceruto
Copy link
Member

yceruto commented Feb 12, 2025

Nice! just wondering what is the difference between this feature and the fluent php config introduced here #40600? is this an extension?

@alexandre-daubois
Copy link
Contributor Author

This new way of proceeding is perhaps even more robust than the fluid configuration. Thanks to the generated array shapes, static analysis tools will be able to be even more precise and advanced with, among other things, the “typing” of arrays with shapes.

I've also found myself with a much better discovery thanks to the autocompletion offered by the shapes. I confess I wasn't expecting a better experience in this aspect than with fluent config. I've even noticed that, on the latter, the IDEs sometimes have trouble autocompleting properly, referring to methods that are sometimes inaccessible (surely due to the static return types that make suggestions difficult to guess sometimes). Although there are still improvements to be made in the main IDEs (PhpStorm and VSCode among others), work is progressing well on their side to provide better support for array shapes. For example, many raised issues in Youtrack are being worked on right now and may make it for version 2025.1.

Work is also underway to bring inline comments into PHPDoc (core maintainer of PhpStan announced working on it and Psalm already supports inline comments), which is excellent news: with one click on one of the configuration functions, you'll have access to all available documentation and options at a glance, without noise. Fluent config is sometimes hard to read with all the PHP noise necessary for it to work.

TL;DR: fluent config is great and I use it myself whenever possible, especially when it comes to creating conditional configuration and that sort of thing. Configuration functions, on the other hand, provide an excellent compromise between discovery, strong static typing and speed of use on simpler configurations. Want to configure framework bundle? Just create a file with return framework([...]); and you're good to go.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Feb 13, 2025

In addition to what @alexandre-daubois wrote:

The fluent php config format did not make it as a replacement for the yaml one in flex recipes because it comes with unique challenges, namely patching / dumping an existing file is next to impossible.

With php arrays, it becomes as trivial as patching yaml.

That's THE reason that made me push to explore this alternative.

@yceruto
Copy link
Member

yceruto commented Feb 13, 2025

Thanks for the detailed explanation. I understand now that it's a better alternative than the fluent php config.

sorry, another question arose after reading your comments and PR description: will PhpStorm be able to autodetect the $this scope here, or do we need to do something special to enable autocomplete for $this->?

@alexandre-daubois
Copy link
Contributor Author

alexandre-daubois commented Feb 13, 2025

Good question, actually this is a mistake you see in the PR description example. Because of how PHP files are loaded by the DI, you have access to $env and $path variables in your PHP file, not $this->env.

@alexandre-daubois
Copy link
Contributor Author

alexandre-daubois commented Feb 14, 2025

Support for inline comments in PhpStan:

Additionally, this issue on Youtrack has been created to ask for Cmd+Click on array keys if defined in a shape : WI-80560 (cc @nicolas-grekas)

@ondrejmirtes
Copy link
Contributor

Yeah, it's coming, it's now at the top of my todolist :)

@dunglas
Copy link
Member

dunglas commented Feb 14, 2025

Also, it's very important easy to dump or encode a config coming from any store (Kubernetes config, YAML or JSON file...) into a PHP array, while it's almost impossible with the fluent interface.

This makes apps more "cloud native".

@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
Contributor Author

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

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.

8 participants