Skip to content
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

Add function is_using to check if an object uses a given trait #23

Closed
wants to merge 2 commits into from
Closed

Add function is_using to check if an object uses a given trait #23

wants to merge 2 commits into from

Conversation

christopherobin
Copy link

Code is mainly a heavily modified version of is_a/is_subclass_of and provide a test.

Documentation

is_using

(PHP 5.4)

is_using — Checks if the object or class name is using this trait

Description

bool is_using ( mixed $object , string $trait_name , bool $allow_string = true );

Checks if the object is using trait trait_name.

Parameters

_object_

A class name or an object instance

_trait___name_

The trait name

_allow___string_

Whether to call autoloader if the class doesn't exist

Return Values

This function returns TRUE if the object object, uses the trait trait_name, FALSE otherwise.

Examples

<?php
// define a trait
trait Bar {
}

// define a class using the trait
class Foo {
  use Bar;
}

// define a child class
class Foo_Child extends Foo {
}

// define a class not using the traot
class Other {

}

// create a new object
$F = new Foo();
$FC = new Foo_Child();

if (is_using($FC, 'Bar')) {
  echo "yes, \$FC is using Bar\n";
} else {
  echo "no, \$FC is not using Bar\n";
}

if (is_using($F, 'Bar')) {
  echo "yes, \$F is using Bar\n";
} else {
  echo "no, \$F is not using Bar\n";
}

if (is_using('Foo', 'Bar')) {
  echo "yes, Foo is using Bar\n";
} else {
  echo "no, Foo is not using Bar\n";
}

if (is_using('Other', 'Bar')) {
  echo "yes, Other is using Bar\n";
} else {
  echo "no, Other is not using Bar\n";
}

The above example will output:

yes, $FC is using Bar
yes, $F is using Bar
yes, Foo is using Bar
no, Other is not using Bar

@nikic
Copy link
Member

nikic commented Mar 28, 2012

Not sure how necessary this is. You can already easily access this information using Reflection:

return in_array($trait, (new ReflectionClass($class))->getTraitNames());

@christopherobin
Copy link
Author

Well is_a and is_subclass_of could also be done through Reflection...
It just makes more sense to have the whole set of methods (or none of them) instead of only some of them.
Moreover this function also check if the parents are using the trait (since as definition, that means that the child use it too).

@johannes
Copy link
Member

is_a() and is_subclass_of() are older then reflection. We have since decided to make reflection the primary source for such information.

@mhlavac
Copy link

mhlavac commented Mar 28, 2012

What about performance issues when using reflections. Isn't faster to use built in function for checking if the class uses specific trait? Specially in loops when you want to know if specific item has given reflection.

@christopherobin
Copy link
Author

is_a and is_subclass_of have been marked a no longer deprecated in 5.3.0 and their signatures changed in 5.3.9 meaning that they are still maintened and will be hard to deprecate in a short time.

Finally while is_a and is_subclass_of are not necessary for checking the type of a parameter in an object as the developer can use instanceof or typed parameters. Right now there is no easy way to check if a parameter provided to a function uses a given trait...

@johannes
Copy link
Member

Yes, reflection creates an extra object which takes CPU time and memory. I didn't close the PR but stated this to see if there are arguments for adding new global functions.

For the mentioned use case a provocative answer: Well, if you have to know it, outside of writing a code documenter/analyzer/.., you're doing something wrong. Traits are "engine level copy and paste" if the runtime behaviour depends on whether the function was written in that class or not you're doing something wrong. Why should we clutter the global namespace if you're doing it wrong? :-)

@mhlavac
Copy link

mhlavac commented Mar 28, 2012

I have to admit that php is already cluttered by global functions and it's bad to add new ones. I've got your point with programmer. It's true that he should check class or it's parents but not specific code in the object itself.

Also you can use interfaces and check them. It's better to check interface and use trait to implement it's methods.

With the global namespace i don't see if there is problem in adding another is_... function. Developers tend to use these functions and they won't know how to check if specific trait is used.

@auroraeosrose
Copy link
Contributor

To further muddle the mix - remember SPL provides "class_uses" - which with a bit of array manipulation can allow you to write a reflectionless version of the reflection code posted above

return in_array($trait, class_uses($classname));

All you're doing by NOT making this available is having everyone write their own version of the call

@dshafik
Copy link
Contributor

dshafik commented Mar 28, 2012

If there a reason not to make this functionality available via a second argument to class_uses() ?

<?php
if (class_uses($classnameOrObject, $traitName)) {
     // returns bool
}
?>

@dshafik
Copy link
Contributor

dshafik commented Mar 28, 2012

I have implemented my previous suggestion in pull request #27

@schmittjoh
Copy link

@johannes Since a trait can basically be seen as an interface with an implementation, I think it makes sense to have something similar to an instanceof operator for traits as well, or being able to type-hint traits (if both of the latter is possible forget my comment, I haven't used traits in PHP yet).

As a side note, for example Scala does not have the notion of interfaces, solely traits.

@MagicalTux
Copy link

Adding is_using() seems to make sense when is_a() already exists, and is still maintained & useful. Also adding an equivalent of instanceof ($a usestrait Foo maybe?) could be interesting (I believe it'd be the same code internally).

@naderman
Copy link

This seems odd to me. Traits in the PHP sense are not interfaces. Even if an object "is_using" a trait, it might have aliased method names, and you cannot rely on the trait's methods being present on the object. If you want to make a check like this, you should add an additional interface and implement it in the class using the trait. Then you can check for the interface with instanceof which makes a lot more sense.

@johannes
Copy link
Member

I don't think that traits should be seen as "interface with an implementation". Seeing it as interface causes trouble with aliasing and such stuff. So even if a trait is used it doesn't mean a specific method will be there, it might not be there at all, it might have another name or another visibility. The trait gies no guarantee. If you need some guarantee enforce it by an interface.

@schmittjoh
Copy link

I have not spend much time with the PHP implementation of traits, but I can see your arguments, and if the trait indeed does not guarantee that the method is also present in the object, then I have to second your opinion that this does not make much sense at runtime.

This also means that you have to duplicate some code on the interface and in the trait which is unfortunate.

@mhlavac
Copy link

mhlavac commented Mar 29, 2012

@naderman I have to agree. You shouldn't ask if specific object uses trait because you can not be sure if methods of that given trait weren't aliased. Therefore it's really bad practice to ask if given object, or class uses trait because you never know how it is used.

The question is if programmers should care more about their code and create interfaces, or if php will be more benevolent to them and it will allow them to use is_using instead (or somekind of $object usestrait 'MyTrait').

@smarr
Copy link

smarr commented Mar 31, 2012

I would like to ask that such proposals are discussed on the internals mailing list.
Otherwise, I will probably miss them.

And as Johannes pointed out, the missing guarantees weaken the value of such information.

The possibility to change the visibility of a method, to declare it as an implementation detail, will require you to use an interface for the intended purpose.

From my point of view, you are not using such checks when you are using PHP as a dynamic language anyway.
If you use it as a more static language, interfaces are the way to go.

If somebody knows how to archive this discussion in the bug tracker, this discussion would hopefully help as documentation, and the pull request could be closed.

@yohgaki
Copy link
Contributor

yohgaki commented Apr 1, 2012

allow_string parameter is not a parameter prevents autoloading but restricts 1st parameter as a string, right?

Preventing autoload is just a side effect of this. I think the doc is not right. I don't like the way is_a(), is_subclass_of() works now, but it should have consistent doc and behavior. (I fixed docs on SVN yesterday)

@christopherobin
Copy link
Author

No, like its name says it just allows the first parameter to be a string and does not restrict it.

if (allow_string && Z_TYPE_P(obj) == IS_STRING) {
    // ...
} else if (Z_TYPE_P(obj) == IS_OBJECT && HAS_CLASS_ENTRY(*obj)) {
    // ...
} else {
    RETURN_FALSE;
}

But as for the autoload yes it is just a side-effect, the method zend_lookup_class used by the string part of the if above uses the autoload by default, the second parameter of the is_a/is_subclass_of methods will never use the autoload whatever value allow_string is used so I agree that the doc is misleading on this.

See is_a_impl, zend_lookup_class_ex and zend_lookup_class

@php-pulls
Copy link

Comment on behalf of johannes at php.net:

The only reason presented is that one wants to use traits like interfaces. This can't be done as a trait doesn't define a contract.
If there are new arguments/reasons please file a bug ticket or RFC with discussion on internals. Thanks.

johannes

@php-pulls php-pulls closed this Apr 2, 2012
@auroraeosrose
Copy link
Contributor

I'm confused - I personally use "does this have this trait" to check for BEHAVIOR not as an interface "can I call this method"

Often when using things in code (especially other people's code) it is useful to know if there is a trait (behavior) attached to the class - oh this has the log trait, I don't want to inject and use my own logger. Oh this has an event trait, so I don't need to inject it my own event system, etc.

@johannes
Copy link
Member

johannes commented Apr 3, 2012

I don't see how a trait gives any information worth checking (besides documentation and other meta things).

I see your idea, Elizabeth, but I fail to see that the existence of a trait gives any relevant information. What I see is that people use traits as "fancy interfaces" and draw wrong conclusions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.