Skip to content

[Downgrade PHP 8.0] Attributes #4156

Description

@leoloso

Feature Request

Downgrade new feature from PHP 8.0 to its equivalent PHP 7.4 code. RFC with explanation

This downgrading must be handled with care, since it may or may not work!. Most likely, annotations can't be handled on a general fashion, so custom rules will need to be created for the project.

Annotations have no equivalent before PHP 8.0, so the downgrade action must remove the code, and possibly add docblocks to represent the information. Please notice that annotations can be added to params too, so the equivalent docblock will be cumbersome:

-#[ExampleAttribute]
+/**
+ * @annotation ExampleAttribute
+ */
class Foo
{
-    #[ExampleAttribute]
+    /**
+     * @annotation ExampleAttribute
+     */
    public const FOO = 'foo';
 
-    #[ExampleAttribute]
+    /**
+     * @annotation ExampleAttribute
+     */
    public $x;
 
-    #[ExampleAttribute]
-    public function foo(#[ExampleAttribute] $bar) { }
+    /**
+     * @annotation ExampleAttribute
+     * @param [type] $bar
+     *   #ExampleAttribute
+     */
+    public function foo($bar) { }
}

Downgrading code will work when removing a Deprecated annotation, since it's needed only during development time:

// an idea, not part of the RFC
use Php\Attributes\Deprecated;
 
-#[Deprecated("Use bar() instead")]
function foo() {}

But it will break the code when the annotation is needed on runtime. For instance, the RFC demonstrates annotations for subscribing to events:

public function addSubscriber(object $subscriber)
{
    $reflection = new ReflectionObject($subscriber);

    foreach ($reflection->getMethods() as $method) {
        // Does this method has Listener attributes?
        $attributes = $method->getAttributes(Listener::class);

        foreach ($attributes as $listenerAttribute) {
            /** @var $listener Listener */
            $listener = $listenerAttribute->newInstance();

            // with $listener instanceof Listener attribute,
            // register the method to the given Listener->event
            // as a callable
            $this->listeners[$listener->event][] = [$subscriber, $method->getName()];
        }
    }
}

To downgrade, this code must read the data from docblocks instead of annotations (getAttributes), and instantiate the classes to retrieve their data (newInstance, $listener->event):

public function addSubscriber(object $subscriber)
{
    $reflection = new ReflectionObject($subscriber);

    foreach ($reflection->getMethods() as $method) {
        // Does this method has Listener attributes?
-        $attributes = $method->getAttributes(Listener::class);
+        /* ??? */

        foreach ($attributes as $listenerAttribute) {
            /** @var $listener Listener */
-            $listener = $listenerAttribute->newInstance();
+            /* ??? */

            // with $listener instanceof Listener attribute,
            // register the method to the given Listener->event
            // as a callable
-            $this->listeners[$listener->event][] = [$subscriber, $method->getName()];
+            $this->listeners[/* ??? */][] = [$subscriber, $method->getName()];
        }
    }
}

This seems to be difficult to generalize... Would it be even possible?

We could follow this strategy:

  • Parse the annotations, and identify if they can be removed from a pre-conceived allowlist (eg: #[Deprecated])
  • Downgrade the ones that can be removed
  • If have no more annotations, downgrade is successful
  • If still have more, downgrade rule must fail, by either:
    • throwing a DowngradeNotImplementedException, asking the developer to implement a custom rule
    • throwing a DowngradeNotPossibleException

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions