php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #75079 self keyword leads to incorrectly generated TypeError when in closure in trait
Submitted: 2017-08-16 18:12 UTC Modified: 2017-12-12 16:51 UTC
Votes:3
Avg. Score:4.7 ± 0.5
Reproduced:3 of 3 (100.0%)
Same Version:3 (100.0%)
Same OS:2 (66.7%)
From: aidantwoods at gmail dot com Assigned:
Status: Closed Package: Class/Object related
PHP Version: 7.1.8 – 7.2.0 OS: OSX, Debian
Private report: No CVE-ID: None
 [2017-08-16 18:12 UTC] aidantwoods at gmail dot com
Description:
------------
Traits are meant to be syntactic sugar for "copy-pasting" things into classes. As such, when the self keyword is used inside a trait, it does not resolve to the name of the trait, but rather resolves to be the name of the class in which the trait was used in.

If the self keyword is used within a type hint in a closure that is used inside a trait, this closure does not appear to be re-resolved per inclusion.

For example, from the test script included, it should not be possible for the closure to throw a TypeError in any circumstance (if a type error is to be thrown, it should be thrown by the outer method)

    public function selfDo(self ...$Selfs)
    {
        array_map(
            function (self $Self) : self
            {
                return $Self;
            },
            $Selfs
        );
    }

As expected, when calling exclusively the following

    $Bar->selfDo($Bar, $Bar);


No TypeError is thrown. Alternatively, when calling exclusively the following

    $Baz->selfDo($Baz, $Baz);

Also no TypeError is thrown.

The problem arises when calling one after the other (does not need to be immediately after).

Depending on the order those last two lines of the test script are called in, you will see a different error thrown. In the order given, the following is generated (trimmed for brevity)

    Fatal error: Uncaught TypeError: Argument 1 passed to Baz::{closure}() must be an instance of Bar, instance of Baz given in[...]

It seems that once the method is initially called, the self type hint in the closure in the trait resolves to be the name of the class in which it was used in (no bug here), the bug is that this initial class name is now permanently resolved as its current value in latter calls to the same method (from a different unrelated class).



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

trait Foo
{
    public function selfDo(self ...$Selfs)
    {
        array_map(
            function (self $Self) : self
            {
                return $Self;
            },
            $Selfs
        );
    }
}

class Bar
{
    use Foo;
}

class Baz
{
    use Foo;
}

$Bar = new Bar;
$Baz = new Baz;


$Bar->selfDo($Bar, $Bar);
$Baz->selfDo($Baz, $Baz);

Actual result:
--------------
Fatal error: Uncaught TypeError: Argument 1 passed to Baz::{closure}() must be an instance of Bar, instance of Baz given in /Users/Aidan/traits.php:8
Stack trace:
#0 [internal function]: Baz->{closure}(Object(Baz))
#1 /Users/Aidan/traits.php(12): array_map(Object(Closure), Array)
#2 /Users/Aidan/traits.php(31): Baz->selfDo(Object(Baz), Object(Baz))
#3 {main}
  thrown in /Users/Aidan/traits.php on line 8

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2017-08-16 18:24 UTC] aidantwoods at gmail dot com
-Summary: self is only resolved once for closure in trait +Summary: self keyword leads to incorrectly generated TypeError when in closure in trait -Operating System: OSX; Kali Linux (Debian) +Operating System: Tested: OSX; Kali Linux (Debian)
 [2017-08-16 18:24 UTC] aidantwoods at gmail dot com
edit: summary
 [2017-11-27 10:15 UTC] caparros at novius dot fr
Hello,

I've also reproduced this bug with both PHP 7.0 and PHP 7.1 (but not PHP 5.6).

trait T {
    public static function test()
    {
        return function (self $x) {
            print_r($x);
        };
    }
}

class A {
    use T;
}

class B {
    use T;
}

$a = A::test();
$b = B::test();

$a(new A());
$b(new B());

Result:

B Object
(
)
Fatal error:  Uncaught TypeError: Argument 1 passed to A::{closure}() must be an instance of B, instance of A given, called in [...][...] on line 24 and defined in
 [2017-12-12 16:51 UTC] aidantwoods at gmail dot com
-Operating System: Tested: OSX; Kali Linux (Debian) +Operating System: OSX, Debian -PHP Version: 7.1.8 +PHP Version: 7.1.8 – 7.2.0
 [2017-12-12 16:51 UTC] aidantwoods at gmail dot com
Bug still present in new minor release.
 [2018-01-15 11:17 UTC] nikic@php.net
Automatic comment on behalf of nikita.ppv@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=20233469737ba007a1bdd38b7acd5512a2e7d534
Log: Fixed bug #75079
 [2018-01-15 11:17 UTC] nikic@php.net
-Status: Open +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Mar 19 04:01:31 2024 UTC