php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #78859 Weird behavior with uninitialized typed properties and __set/__get
Submitted: 2019-11-22 21:05 UTC Modified: 2019-11-22 21:25 UTC
From: krmgns at gmail dot com Assigned:
Status: Not a bug Package: *General Issues
PHP Version: 7.4.0RC4 OS: Ubuntu 18.04
Private report: No CVE-ID: None
 [2019-11-22 21:05 UTC] krmgns at gmail dot com
Description:
------------
Seems calling a constructor is triggering __set magic for uninitialized typed properties. So it does not matter the property is public or private/protected.

I suppose the problem is __set/__get called before __construct when a type is given to a property. Also I if remove __get then I get object(acme\Options)#1 (1) { ["stack"]=> array(1) { ["stack"]=> array(1) { ["one"]=> int(1) } } }.


Test script:
---------------
final class Options {
    // This is OK but why redundant initialization?
    // public array $stack = [];
    public array $stack;

    public function __construct(array $stack) {
        $this->stack = $stack;
    }

    public function __set(string $name, $value) {
        // if (empty($this->stack)) {
        //     // This line below yields: object(acme\Options)#1 (0) { ["stack"]=> uninitialized(array) }.
        //     // return;
        //     // This line below yields: object(acme\Options)#1 (1) { ["stack"]=> array(1) { ["stack"]=> array(1) { ["one"]=> int(1) } } }.
        //     // $this->stack = [];
        // }

        // This is solving problem but the purpose is not that also corrupting $stack structure inserting a new sub-array.
        // object(acme\Options)#1 (1) { ["stack"]=> array(2) { ["one"]=> int(1) ["stack"]=> array(1) { ["one"]=> int(1) } } }
        // $this->stack = $value;

        // This is indicating that __set called before (before __construct).
        // throw new \Exception();

        // This is problematic part.
        $this->stack[$name] = $value;
    }

    public function __get(string $name) {
        // This is indicating that __get called before (before __construct).
        // throw new \Exception();

        return $this->stack[$name] ?? null;
    }
}

var_dump(new Options(['one' => 1]));


Expected result:
----------------
object(acme\Options)#1 (1) {
  ["stack"]=>
  array(1) {
    ["one"]=>
    int(1)
  }
}

Actual result:
--------------
PHP Notice:  Indirect modification of overloaded property acme\Options::$stack has no effect in /var/www/a.php on line 19
PHP Fatal error:  Uncaught TypeError: Typed property acme\Options::$stack must be array, null used in /var/www/a.php:19
Stack trace:
#0 /var/www/a.php(16): acme\Options->__set()
#1 /var/www/a.php(26): acme\Options->__construct()
#2 {main}
  thrown in /var/www/a.php on line 19

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-11-22 21:25 UTC] requinix@php.net
-Status: Open +Status: Not a bug -PHP Version: 7.4.0RC6 +PHP Version: 7.4.0RC4
 [2019-11-22 21:25 UTC] requinix@php.net
Thank you for taking the time to report a problem with PHP.
Unfortunately you are not using a current version of PHP --
the problem might already be fixed. Please download a new
PHP version from http://www.php.net/downloads.php

If you are able to reproduce the bug with one of the latest
versions of PHP, please change the PHP version on this bug report
to the version you tested and change the status back to "Open".
Again, thank you for your continued support of PHP.

https://3v4l.org/VJFBK
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Mon Jul 06 06:01:29 2020 UTC