# bug 48541 - critical for PHP 5.3

- Thread: https://externals.io/message/44258
- Original archive: https://news-web.php.net/php.internals/44258
- Started by Greg Beaver on 2009-06-13T06:13:53+00:00
- Messages: 6
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #44258 — Greg Beaver — 2009-06-13T06:13:53+00:00

URL: https://externals.io/message/44258#44258

Hi,

I didn't mark http://bugs.php.net/48541 as critical because it is not a
critical bug, but it is critical that this thing be fixed in PHP 5.3.

I can do it if someone can answer this question: how do closures
uniquely identify themselves?  spl_autoload_register is mistakenly
treating all closures as if they were a single copy of the static method
"Closure::__invoke", and so only the first registered closure is ever
called (plus it leaks the other closures at shutdown).  If the system
can be made to better identify the closure, then spl_autoload_register
can use that (and also properly free the refcount on a dupe).

Greg

---

## #44260 — Christian Seiler — 2009-06-13T11:17:29+00:00

URL: https://externals.io/message/44258#44260 · In reply to: #44258

Hi Greg,

> I can do it if someone can answer this question: how do closures
> uniquely identify themselves?  spl_autoload_register is mistakenly
> treating all closures as if they were a single copy of the static method
> "Closure::__invoke", and so only the first registered closure is ever
> called (plus it leaks the other closures at shutdown).  If the system
> can be made to better identify the closure, then spl_autoload_register
> can use that (and also properly free the refcount on a dupe).

A closure can only be uniquely identified by the object storage id the
object has. You cannot assume any identity with regard to, for example,
file name and line where it was defined, since one could imagine the
following:

foreach ($dirs as $dir) {
  spl_autoload_register (function ($class) use ($dir) {
     include $dir.'/'.$class.'.php';
  });
}

(or something like it)

If nobody else does it I'll fix this later today.

Regards,
Christian

---

## #44262 — Greg Beaver — 2009-06-13T16:50:00+00:00

URL: https://externals.io/message/44258#44262 · In reply to: #44260

Christian Seiler wrote:
> Hi Greg,
> 
> > […]
> 
> A closure can only be uniquely identified by the object storage id the
> object has. You cannot assume any identity with regard to, for example,
> file name and line where it was defined, since one could imagine the
> following:
> 
> foreach ($dirs as $dir) {
>   spl_autoload_register (function ($class) use ($dir) {
>      include $dir.'/'.$class.'.php';
>   });
> }
> 
> (or something like it)
> 
> If nobody else does it I'll fix this later today.

Hi,

Thanks Christian, I've got a patch and a test I'm about to commit, would
be great if you can review it.

Thanks,
Greg

---

## #44264 — Christian Seiler — 2009-06-13T17:54:27+00:00

URL: https://externals.io/message/44258#44264

Hi Greg,

> (I meant commit when I said patch, sorry :)
> 
> http://news.php.net/php.cvs/58696
> and
> http://news.php.net/php.cvs/58697

Thanks, looks fine to me. However, I noticed that closures don't work
with `spl_autoload_unregister()` and `spl_autoload_functions()`. Also,
classes that define __invoke work with `spl_autoload_functions()` but not
with `spl_autoload_unregister()` - unless array ($o, '__invoke') is given
to `spl_autoload_register()` directly, then the same will work for unregister.

Or, to sum it up:

i) Real Closures

$c = function ($class) { var_dump ('foo'); }
spl_autoload_register ($c);

var_dump (spl_autoload_functions ()); // '{closure}'

spl_autoload_unregister ($c); // no effect

ii) Invokables

WORKS:

<?php

class Autoloader {
  private $dir;
  public function __construct ($dir) {
    $this->dir = $dir;
  }
  public function __invoke ($class) {
    var_dump ("{$this->dir}/$class.php");
  }
}

$al1 = new Autoloader ('d1');
$al2 = new Autoloader ('d2');

spl_autoload_register (array ($al1, '__invoke'));
spl_autoload_register (array ($al2, '__invoke'));

var_dump (spl_autoload_functions ());

spl_autoload_unregister (array ($al1, '__invoke'));

$x = new Test;

?>

SEMI-WORKS:

<?php

class Autoloader {
  private $dir;
  public function __construct ($dir) {
    $this->dir = $dir;
  }
  public function __invoke ($class) {
    var_dump ("{$this->dir}/$class.php");
  }
}

$al1 = new Autoloader ('d1');
$al2 = new Autoloader ('d2');

spl_autoload_register ($al1);
spl_autoload_register ($al2);

var_dump (spl_autoload_functions ());
   // gives array ($object, '__invoke') instead of
   // directly $object - but that's at least equivalent

spl_autoload_unregister ($al1);
   // no effect
spl_autoload_unregister (array ($al1, '__invoke'));
   // no effect

$x = new Test;

?>

Regards,
Christian

---

## #44267 — Greg Beaver — 2009-06-13T18:11:53+00:00

URL: https://externals.io/message/44258#44267 · In reply to: #44264

Christian Seiler wrote:
> Hi Greg,
>
>   
> > […]
>
> Thanks, looks fine to me. However, I noticed that closures don't work
> with `spl_autoload_unregister()` and `spl_autoload_functions()`. Also,
> classes that define __invoke work with `spl_autoload_functions()` but not
> with `spl_autoload_unregister()` - unless array ($o, '__invoke') is given
> to `spl_autoload_register()` directly, then the same will work for unregister.
>   
Hi,

I first replied off-list accidentally.  I wonder if the problem is that
spl_autoload_register should not just be checking to see if zcallable is
IS_OBJECT, but also if it is an instance of Closure.  That should clear
up the examples below (spl_autoload_register($blah) where $blah is not a
closure should not work).  spl_autoload_unregister probably just needs a
cut/paste of the code I committed to spl_autoload_register for munging a
closure name with the addition of Closure instanceof check.

Also, those examples would make great .phpt tests :).

Greg
> Or, to sum it up:
>
> i) Real Closures
>
> $c = function ($class) { var_dump ('foo'); }
> spl_autoload_register ($c);
>
> var_dump (spl_autoload_functions ()); // '{closure}'
>
> spl_autoload_unregister ($c); // no effect
>
> ii) Invokables
>
> WORKS:
>
> <?php
>
> class Autoloader {
>   private $dir;
>   public function __construct ($dir) {
>     $this->dir = $dir;
>   }
>   public function __invoke ($class) {
>     var_dump ("{$this->dir}/$class.php");
>   }
> }
>
> $al1 = new Autoloader ('d1');
> $al2 = new Autoloader ('d2');
>
> spl_autoload_register (array ($al1, '__invoke'));
> spl_autoload_register (array ($al2, '__invoke'));
>
> var_dump (spl_autoload_functions ());
>
> spl_autoload_unregister (array ($al1, '__invoke'));
>
> $x = new Test;
>
> ?>
>
> SEMI-WORKS:
>
> <?php
>
> class Autoloader {
>   private $dir;
>   public function __construct ($dir) {
>     $this->dir = $dir;
>   }
>   public function __invoke ($class) {
>     var_dump ("{$this->dir}/$class.php");
>   }
> }
>
> $al1 = new Autoloader ('d1');
> $al2 = new Autoloader ('d2');
>
> spl_autoload_register ($al1);
> spl_autoload_register ($al2);
>
> var_dump (spl_autoload_functions ());
>    // gives array ($object, '__invoke') instead of
>    // directly $object - but that's at least equivalent
>
> spl_autoload_unregister ($al1);
>    // no effect
> spl_autoload_unregister (array ($al1, '__invoke'));
>    // no effect
>
> $x = new Test;
>
> ?>
>
> Regards,
> Christian

---

## #44268 — Christian Seiler — 2009-06-13T20:25:58+00:00

URL: https://externals.io/message/44258#44268 · In reply to: #44267

Hi,

[quote from off-list:]
> I am in way over my head and would not do a good job
> fixing these (or have the time), care to take a crack at it?

Ok, I'll do that tomorrow morning.

> I wonder if the problem is that
> spl_autoload_register should not just be checking to see if zcallable is
> IS_OBJECT, but also if it is an instance of Closure.  That should clear
> up the examples below (spl_autoload_register($blah) where $blah is not a
> closure should not work).

No, actually it should definitely work, since $blah () works when the
class defined __invoke and objects of this class are thus callable.

Regards,
Christian
