symfony icon indicating copy to clipboard operation
symfony copied to clipboard

[Config] Allow scalar configuration in PHP Configuration

Open jderusse opened this issue 3 years ago • 9 comments

Q A
Branch? 6.1
Bug fix? no
New feature? yes
Deprecations? no
Tickets -
License MIT
Doc PR -

By using beforeNormalization hook in the ConfigBuilder developers could allow end-user to enter any value, and fix it to match the schema.

This is super powerful and provides a great DX for end users by providing "shortcuts" for configuration.

For instance, it allows users to declare

framework:
  lock: flock

instead of

framework:
  lock:
    resources:
      default:
        - flock

On another hand, the new Php configuration generated from ConfigBuild to declare configuration via PHP files provides another great UX. But sadly lack the previous behavior. People have to enter the full configuration.

This PR relax (when needed) the type-hint on classes and methods generated by the ConfigBuilderGenerator to let people use the same shortCut in their PHP configuration:

For instance

 return static function (FrameworkConfig $framework): void {
-    $framework->lock()
-        ->resource('default', ['%env(LOCK_DSN)%'])
+    $framework->lock('%env(LOCK_DSN)%');
 };

or

 return static function (FrameworkConfig $framework): void {
     $subscriptionWorkflow = $framework->workflows()->workflows('subscription');

-    $subscriptionWorkflow->place()->name(Subscription::STATUS_SUBMITTED);
-    $subscriptionWorkflow->place()->name(Subscription::STATUS_CANCELED);
-    $subscriptionWorkflow->place()->name(Subscription::STATUS_ACCEPTED);
-    $subscriptionWorkflow->place()->name(Subscription::STATUS_VALIDATED);
+    $subscriptionWorkflow->place(Subscription::STATUS_SUBMITTED);
+    $subscriptionWorkflow->place(Subscription::STATUS_CANCELED);
+    $subscriptionWorkflow->place(Subscription::STATUS_ACCEPTED);
+    $subscriptionWorkflow->place(Subscription::STATUS_VALIDATED);

One last thing to take into account: When people configure an entry with a scalar value, then it does not make sense to configure the child object. ie.

$framework->lock('%env(LOCK_DSN)%')
   ->resource('default', ['%env(LOCK_DSN)%']); // !!! what does that mean ? It's even not possible in yaml nor XML

To solve the issue, we could throw an exception inside resource if the child has been initialized with a scalar value.

But :bulb: I decided to NOT return the child in such a situation... In that case, the user could even chain the "shortcuts"

 return static function (FrameworkConfig $framework): void {
     $subscriptionWorkflow = $framework->workflows()->workflows('subscription');

     $subscriptionWorkflow->place(Subscription::STATUS_SUBMITTED);
-    $subscriptionWorkflow->place(Subscription::STATUS_CANCELED);
-    $subscriptionWorkflow->place(Subscription::STATUS_ACCEPTED);
-    $subscriptionWorkflow->place(Subscription::STATUS_VALIDATED);
+      ->place(Subscription::STATUS_CANCELED)
+      ->place(Subscription::STATUS_ACCEPTED)
+      ->place(Subscription::STATUS_VALIDATED);

Draw back is, the signature of the method place is now place(mixed $value = []): self|\Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig Is it an issue?

jderusse avatar Nov 20 '21 18:11 jderusse

Related to #43164?

Draw back is, the signature of the method place is now place(mixed $value = []): self|\Symfony\Config\Framework\Workflows\WorkflowsConfig\PlaceConfig Is it an issue?

IMHO it's a step back yes.

Is shortcuts worth pursuing? Rather than fully qualified/explicit config?

But sadly lack the previous behavior. People have to enter the full configuration.

works for me :)

ro0NL avatar Nov 21 '21 07:11 ro0NL

Actually, I converted a personal application to PHP configuration, and have to admit that I've been disappointed by the experience.

Some really simple YAML configuration framework.lock: flock become obscure $framework->lock()->resource('default', ['flock']) <= why resource, why default why flock is wrapped into an array?

Some configurations short monolog.handler.main.excluded_http_codes: [403, 404] become super verbose with intermediate varianbles

    $mainHandler = $monolog->handler('main');
    $mainHandler->excludedHttpCode()->code(403);
    $mainHandler->excludedHttpCode()->code(404);

Moreover, when I switched my configuration, the symfony documentation was not-yet updated, and I had to dig into the Configuration.php file to understand how to convert my yaml file into PHP... Now this will be easier for symfony (because most of config are now documented) but this is not the case for many other bundles.

If the issue is only about the double return signature xxx(): self|child we could keep the current behavior, and throw an exception when people try to update a property inside the shortcut value

$framework->lock('redis://localhost')->resource(...); <== InvalidConfigurationException

jderusse avatar Nov 21 '21 13:11 jderusse

Rebase needed, in case there is anything left after #46328

nicolas-grekas avatar May 17 '22 13:05 nicolas-grekas

Friendly ping @jderusse /cc @HypeMC

Is this PR still needed after https://github.com/symfony/symfony/pull/46328?

nicolas-grekas avatar Jul 28 '22 09:07 nicolas-grekas

@nicolas-grekas This PR provides some additional features that #46328 doesn't have, eg allowed type hints on generated methods: https://github.com/symfony/symfony/blob/962834b63066c1be04bed19cbc6762d3136bcfa3/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypesConfig.php#L25

Currently mixed is used in these situations: https://github.com/symfony/symfony/blob/5bf31c57c3d07ab6a04bdeb8fdc2957e0591dcb5/src/Symfony/Component/Config/Tests/Builder/Fixtures/ScalarNormalizedTypes/Symfony/Config/ScalarNormalizedTypesConfig.php#L31

My PR only covers using scalars with the beforeNormalization hook.

HypeMC avatar Jul 28 '22 10:07 HypeMC

Would you be able to take over the PR @HypeMC? I feel like @jderusse would appreciate :)

nicolas-grekas avatar Jul 28 '22 10:07 nicolas-grekas

This PR also adds an (opiniated) change in the returned object: When people configure an entry with a scalar value, then, the returned value is the object and not the child object. This allows chaining "aliases". ie

     $subscriptionWorkflow = $framework->workflows()->workflows('subscription');

     $subscriptionWorkflow->place(Subscription::STATUS_SUBMITTED);
-    $subscriptionWorkflow->place(Subscription::STATUS_CANCELED);
-    $subscriptionWorkflow->place(Subscription::STATUS_ACCEPTED);
-    $subscriptionWorkflow->place(Subscription::STATUS_VALIDATED);
+      ->place(Subscription::STATUS_CANCELED)
+      ->place(Subscription::STATUS_ACCEPTED)
+      ->place(Subscription::STATUS_VALIDATED);

jderusse avatar Jul 28 '22 10:07 jderusse

Would you be able to take over the PR @HypeMC? I feel like @jderusse would appreciate :)

Sure, if @jderusse doesn't mind, I'll take over.

HypeMC avatar Jul 28 '22 10:07 HypeMC

if @jderusse doesn't mind,

no issue at all, thanks for your help

jderusse avatar Jul 28 '22 13:07 jderusse

PR rebased and duplicated logic with #46328 removed

jderusse avatar Oct 23 '22 17:10 jderusse

Thank you @jderusse.

nicolas-grekas avatar Oct 24 '22 07:10 nicolas-grekas

@jderusse

Before in \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailedConfig::httpCode was:

public function httpCode(string $code, mixed $value = []): \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig|static

Now it generates:

public function httpCode(string $code, array $value = []): \Symfony\Config\Framework\HttpClient\ScopedClientConfig\RetryFailed\HttpCodeConfig|static

Is this a bug in the config generator or in the HttpClient?

alexndlm avatar Aug 31 '23 16:08 alexndlm