php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #53009 Weird behavior of method_exists with anonymous functions
Submitted: 2010-10-07 10:27 UTC Modified: 2010-10-08 02:19 UTC
From: acid24 at gmail dot com Assigned:
Status: Wont fix Package: Class/Object related
PHP Version: 5.3.3 OS: Ubuntu 10.04
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2010-10-07 10:27 UTC] acid24 at gmail dot com
Description:
------------
method_exists() has a weird behavior when used on anonymous functions. One would expect that testing the variable that holds the anonymous function and the class of the anonymous function (Closure) to either have or not the __invoke method. Instead method_exists() reports that the variable that holds the anonymous function HAS an __invoke method and the class of the anonymous function HAS NOT an __invoke method. Is this behavior correct? If yes, maybe somebody can explain why. Thank you in advance.

Test script:
---------------
<?php

$lambda = function() {};

var_dump( method_exists( $lambda, '__invoke' ) );
var_dump( method_exists( get_class( $lambda ), '__invoke' ) );

Expected result:
----------------
boolean(false)
boolean(false)

or 

boolean(true)
boolean(true)

Actual result:
--------------
boolean(true)
boolean(false)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-10-07 10:44 UTC] acid24 at gmail dot com
-: acid24ro@gmail.com +: acid24 at gmail dot com
 [2010-10-07 10:44 UTC] acid24 at gmail dot com
provided wrong email address
 [2010-10-07 14:46 UTC] cataphract@php.net
In PHP, not all methods are created equal.

Normal classes have a structure wherein the declared methods are stored -- the class entry function table. method_exists returns true if a method exists in such table.

But there's another way to obtain methods -- through the get_method handler. See http://wiki.php.net/internals/engine/objects#get_method
method_exists(), if given an instance, calls this handler.

The handler table is associated to instances only; from the class entry, you cannot know which handler table would be used in instances of that class. 

Therefore, it's possible to have method_exists() return true if given an instance and false if given the class of that instance as a string.

In this case, there's a twist. method_exists() also returns false if the  function given by the get_method handler has the flag ZEND_ACC_CALL_VIA_HANDLER, which means the method was generated on-the-fly. This is the case with Closure's __invoke, but the implementation of method_exists has a special exception for the Closure class, and returns true there.

This could be "fixed" by removing the exception and returning false all the time, but this wouldn't fix the bigger issue -- that you can have method_exists returns true with an object and false with the class of the object. So I'd mark this Wont Fix, but let's see if someone thinks otherwise.
 [2010-10-08 02:19 UTC] cataphract@php.net
-Status: Open +Status: Wont fix
 [2010-10-08 09:04 UTC] acid24 at gmail dot com
Thank you for the clear explanation. 

For anyone interested I found that Reflection gives me the "correct" behavior.

<?php
$lambda = function() {};

$r = new ReflectionObject( $lambda );
var_dump( $r->hasMethod( '__invoke' ) );
$r = new ReflectionClass( get_class( $lambda ) );
var_dump( $r->hasMethod( '__invoke' ) );
?>

outputs

bool(false)
bool(false)
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Sun Oct 24 07:03:34 2021 UTC