BackwardCompatibilityCheck icon indicating copy to clipboard operation
BackwardCompatibilityCheck copied to clipboard

Using `ENUM`s as class property triggers BC failure

Open alexmerlin opened this issue 4 months ago • 5 comments

Having a class property of type (Backed)Enum:

class SomeClass
{
    protected VisibilityEnum $visibility = VisibilityEnum::Public;
}

triggers backwardCompatibilityCheck error in my CI action:

Running roave-backward-compatibility-check --from=1.0 --install-development-dependencies

[BC] SKIPPED: An enum expression Namespace\VisibilityEnum::Public is not supported in class Namespace\Example in file /src/SomeClass.php (line 20)

Can I disable the BC check on specific files? Or do I need to switch from using Enums to constants?

alexmerlin avatar Aug 21 '25 13:08 alexmerlin

Related to https://github.com/laminas/laminas-continuous-integration-action/issues/310

alexmerlin avatar Aug 22 '25 09:08 alexmerlin

Can I disable the BC check on specific files?

There's a baseline feature for that, but generally, the tool will refuse to evaluate any non-constant expressions. ENUMs are effectively instances of a class, and require evaluating that class at runtime to be instantiated.

Ocramius avatar Aug 22 '25 09:08 Ocramius

Copying from discussion held elsewhere:

Dilemma: I have a piece of code like

class SomeClass
{
    protected VisibilityEnum $visibility = VisibilityEnum::Public;
}

BetterReflection will categorically refuse to give you the value of $visibility, since it requires autoloading VisibilityEnum, and instantiating one of its values.

The reasons are multiple, but most notably:

  1. loading code can lead to a security issue, and BR is designed to analyze code from also potentially malicious parties
  2. VisibilityEnum may exist multiple times, when inspecting multiple codebases (or different versions of the same library), therefore leading to conflicts

PHP has multiple of such cases (most notably default values) in which a declaration holds a runtime expression

function foo($time = new DateTimeImmutable()) {

I'm wondering if there's a way to sandbox this expression in a way that is:

  1. safe for the system to evaluate
  2. does not cause any sort of side-effects (like declaring VisibilityEnum in the global scope)

Ocramius avatar Aug 22 '25 10:08 Ocramius

Thanks @Ocramius

I understand the reasoning of why BetterReflection is not able to identify the exact value of $visibility just by looking at the property, without its value specified. I would expect the behaviour you described from this code:

protected VisibilityEnum $visibility;

But when I'm specifying that the property $visibility is of type VisibilityEnum and has this exact value VisibilityEnum::Public.

protected VisibilityEnum $visibility = VisibilityEnum::Public;

In this case I would expect BetterReflection to see that $visibility is not just of type VisibilityEnum, but it is initialized with a specific value of that Enum.

alexmerlin avatar Aug 22 '25 10:08 alexmerlin

In this case I would expect BetterReflection to see that $visibility is not just of type VisibilityEnum, but it is initialized with a specific value of that Enum.

The problem is that ReflectionProperty#getDefaultValue() cannot be used.

I'm thinking of designing something like ReflectionProperty#getDefaultValueSnapshot() or such, as an API that "observes" the expression 🤔

Ocramius avatar Aug 22 '25 11:08 Ocramius