Skip to content

ext/reflection: add getLine / getFileName / isUserDefined to reflection attribute #13889

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

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

Conversation

joelwurtz
Copy link

@joelwurtz joelwurtz commented Apr 5, 2024

This add those methods to the ReflectionAttribute

class ReflectionAttribute implements Reflector
{
    //...
    public function isUserDefined(): bool {}
    public function getFileName(): string|false {}
    public function getLine(): int {}
}

Those method can be really useful when a library want to throw an exception during the analysis of an attribute so the library can show to the user which attribute specifically induce this exception (like: "Attribute at line %d in file %s cause a bad configuration")

Does this need a RFC ?

Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

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

Hi @joelwurtz. Thank you for your contribution! I think this doesn't necessarily need an RFC if nobody complains, but it needs at least an e-mail to the internals mailing list.

@joelwurtz joelwurtz force-pushed the feat/reflect-attribute-filename branch from c064c11 to ce931cd Compare April 5, 2024 12:37
@joelwurtz joelwurtz force-pushed the feat/reflect-attribute-filename branch from ce931cd to 7b216a2 Compare April 5, 2024 12:54
@@ -1956,7 +1958,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getAttributes)

reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU,
fptr->common.attributes, 0, fptr->common.scope, target,
fptr->type == ZEND_USER_FUNCTION ? fptr->op_array.filename : NULL);
fptr->type == ZEND_USER_FUNCTION ? fptr->op_array.filename : NULL, ZEND_USER_CODE(fptr->type));
Copy link
Member

Choose a reason for hiding this comment

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

Actually, I'm a bit confused by the intention here. Does this check whether the attribute class is internal, or whether the target is internal? It currently does the latter, but that doesn't seem super useful?

Copy link
Author

@joelwurtz joelwurtz Apr 5, 2024

Choose a reason for hiding this comment

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

The intention is more : does the attribute (the instance not the class) as been defined (written) in user land code (wether it's a method / parameter / class / etc ...),

If it's an attribute from user land on an user land code it should return true
If it's an attribute from php core on an user land code it should return true
If it's an attribute from php core on php core it should return false
If it's an attribute from user land code on php core it should return false (but i don't think this use case is possible)

Copy link
Member

Choose a reason for hiding this comment

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

Can you clarify why this matters? I understand that internal attributes can have additional validation without instanciation. But why does it matter whether the attrbute is placed on a user or internal target?

Copy link
Author

@joelwurtz joelwurtz Apr 6, 2024

Choose a reason for hiding this comment

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

This is mainly if some library that do error handling (like https://github.com/symfony/error-handler by example) want to provide a generic way to pin point those error, they could then only do that when they detect that the attribute has been writed by the user

But i'm wondering if PHP internal class may have internal attributes attached to their method or class (now or in the future) defined ? I know there is pre defined attributes by PHP but not sure if there are used in internal methods or if an extension can declare them using C code ?

If not then this is useless

Copy link
Member

Choose a reason for hiding this comment

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

Yes, internal attributes can be attached to internal classes, but instantiating them should never error. If it does error, that would be a bug in core/the extension itself. I'm still confused about when and why exactly Symfony error handler would need this information.

Can you provide a concrete example? Moreover, whether the target is internal would already be known, by looking at the reflection of the class/method itself.

Copy link
Author

@joelwurtz joelwurtz Apr 8, 2024

Choose a reason for hiding this comment

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

As an example we could, in a generic error handler, do the following interface :

interface LinkReflectionAttribute
{
   public function getAttribute(): \ReflectionAttribute;
}

When the error handler catch an exception with this interface it may do special things like show the exception (like before) and add a special text (or HTML in web context) to the user to show which attributes this exception concern (it could even show the portion of code in web context).

Library would only have to implement this interface in their exception and the error handler would do the rest

Having the isUserDefined() method would allow this generic handler to be sure that the reflection attribute given has been written in a PHP file and does not originate from the core (in the last case there is no point showing something since it's not written in a PHP file and so it's not fixable by the user)

This method may not be necessary if the interface look like this :

interface LinkReflectionAttribute
{
   public function getAttribute(): \ReflectionAttribute;
   
   public function getTarget(): \ReflectionClass | \ReflectionMethod |\ReflectionFunction | \ReflectionParameter | \ReflectionProperty
}

In this case we could determine if it's user defined given the specific target.

The disadvantage, is that user implementing this interface would need to track every Attribute with their Target, and in some cases it may involved passing those 2 values a lot of times which is cumbersome.

But maybe the isUserDefined() method is not the best approach. Wouldn't it be better if instead we have this getTarget() method inside the ReflectionAttribute class ?

So we could have the target of the attribute without having to keep track of it in PHP code ?

There is already a getTarget() method that returns an int, there could be a getDeclaringTarget() that returns \ReflectionClass | \ReflectionMethod |\ReflectionFunction | \ReflectionParameter | \ReflectionProperty

@joelwurtz joelwurtz force-pushed the feat/reflect-attribute-filename branch from 7b216a2 to fd42159 Compare April 5, 2024 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
3 participants