Skip to content

Conversation

@OskarStark
Copy link
Contributor

@OskarStark OskarStark commented Nov 29, 2025

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

Spotted in symfony/ai#1013 ➡️ https://github.com/symfony/ai/actions/runs/19780810683/job/56680686767?pr=1013

It works for redis, but not for postgres, because redis is using ->values(Distance::cases()) and postgres is using ->enumFqcn(PostgresDistance::class):

Redis

Enum

enum Distance: string
{
    use Comparable;

    case Cosine = 'COSINE';
    case L2 = 'L2';
    case Ip = 'IP';
}

Config

->enumNode('distance')
    ->info('Distance metric to use for vector similarity search')
    ->values(Distance::cases())
    ->defaultValue(Distance::Cosine)
->end()

RESULT

 *         redis?: array<string, array{ // Default: []
 *             connection_parameters?: mixed, // see https://github.com/phpredis/phpredis?tab=readme-ov-file#example-1
 *             client?: string, // a service id of a Redis client
 *             index_name: string,
 *             key_prefix?: string, // Default: "vector:"
 *             distance?: \Symfony\AI\Store\Bridge\Redis\Distance::Cosine|\Symfony\AI\Store\Bridge\Redis\Distance::L2|\Symfony\AI\Store\Bridge\Redis\Distance::Ip, // Distance metric to use for vector similarity search // Default: "COSINE"
 *         }>,

Postgres

Enum

enum Distance: string
{
    use Comparable;

    case Cosine = 'cosine';
    case InnerProduct = 'inner_product';
    case L1 = 'l1';
    case L2 = 'l2';

    public function getComparisonSign(): string
    {
        return match ($this) {
            self::Cosine => '<=>',
            self::InnerProduct => '<#>',
            self::L1 => '<+>',
            self::L2 => '<->',
        };
    }
}

Config

->enumNode('distance')
    ->info('Distance metric to use for vector similarity search')
    ->enumFqcn(PostgresDistance::class)
    ->defaultValue(PostgresDistance::L2)
->end()

RESULT

 *         postgres?: array<string, array{ // Default: []
 *             dsn?: string,
 *             username?: string,
 *             password?: string,
 *             table_name: string,
 *             vector_field?: string,
 *             distance?: cosine|inner_product|l1|l2, // Distance metric to use for vector similarity search // Default: "l2"
 *             dbal_connection?: string,
 *         }>,

you can see, that the result is different:
FQCN:
distance?: \Symfony\AI\Store\Bridge\Redis\Distance::Cosine|\Symfony\AI\Store\Bridge\Redis\Distance::L2|\Symfony\AI\Store\Bridge\Redis\Distance::Ip,
vs. strings:
distance?: cosine|inner_product|l1|l2,

Why?

@alexandre-daubois
Copy link
Member

you can see, that the result is different

The reason is that EnumNode exists for quite some time now and it was used before enum were introduced to only accept a set of scalar values (but no native enum support). Providing ->values(Enum::cases()) set the enum cases as permissible values. Using enumFqcn is actually quite different as it converts each enum case to their scalar value:

    public function getPermissibleValues(string $separator, bool $trim = true): string
    {
        if (is_subclass_of($this->enumFqcn, \BackedEnum::class)) {
            return implode($separator, array_column($this->enumFqcn::cases(), 'value'));
        }

        // ...
    }

I think it is better because when dealing with configuration, you don't necessarily want to expose the whole underlying enum FQCN and deal with long class names. Hope this helps!

@stof
Copy link
Member

stof commented Dec 1, 2025

When using enumFqcn, the config actually accepts both the backing values or an enum instance. So the generated type should combine the set of backing values and the enum FQCN (or it should even generated it as value-of<EnumFqcn>|EnumFqcn, relying on the value-of<> type supported by all SA tools, instead of resolving the list of backing values manually).

@OskarStark
Copy link
Contributor Author

So my fix is correct, when I interpret your comment @alexandre-daubois, right?

While @stof your comment is an improvement I can implement if wanted

@OskarStark
Copy link
Contributor Author

When using enumFqcn, the config actually accepts both the backing values or an enum instance. So the generated type should combine the set of backing values and the enum FQCN (or it should even generated it as value-of|EnumFqcn, relying on the value-of<> type supported by all SA tools, instead of resolving the list of backing values manually).

Like this @stof ?

Copy link
Member

@alexandre-daubois alexandre-daubois left a comment

Choose a reason for hiding this comment

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

Looks good! 🙂

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.

Minor suggestion. Also: tests are failing.

--- a/src/Symfony/Component/Config/Definition/EnumNode.php
+++ b/src/Symfony/Component/Config/Definition/EnumNode.php
@@ -85,14 +85,12 @@ class EnumNode extends ScalarNode

             $values = array_column($this->enumFqcn::cases(), 'value');

-            return implode($separator, array_map(static function ($value) {
-                return json_encode($value, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
-            }, $values));
+            return implode($separator, array_map(static fn ($value) => json_encode($value, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), $values));
         }

         return implode($separator, array_unique(array_map(static function ($value) use ($trim) {
             if (!$value instanceof \UnitEnum) {
-                return json_encode($value);
+                return json_encode($value, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION);
             }

             return $trim ? ltrim(var_export($value, true), '\\') : var_export($value, true);

@OskarStark
Copy link
Contributor Author

Failures unrelated

@nicolas-grekas
Copy link
Member

Thank you @OskarStark.

@nicolas-grekas nicolas-grekas merged commit 3ad9f69 into symfony:7.4 Dec 5, 2025
6 of 12 checks passed
@OskarStark OskarStark deleted the fix/backend-enums branch December 5, 2025 08:23
This was referenced Dec 7, 2025
OskarStark added a commit to symfony/ai that referenced this pull request Dec 7, 2025
…karStark)

This PR was merged into the main branch.

Discussion
----------

 Use Symfony 7.4 for demo, examples and ai.symfony.com

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| Docs?         | no
| Issues        | --
| License       | MIT

### Needs
- [x] symfony/symfony#62563

Commits
-------

ab840cc minor: Require Symfony 7.4 as minimum version
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.

6 participants