php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #78744 Unexpected __set & __get behavior
Submitted: 2019-10-23 16:30 UTC Modified: 2019-10-23 17:48 UTC
From: dirk dot gerigk at tui dot com Assigned:
Status: Not a bug Package: Class/Object related
PHP Version: 7.4.0RC4 OS: Linux
Private report: No CVE-ID: None
 [2019-10-23 16:30 UTC] dirk dot gerigk at tui dot com
Description:
------------
Issue is that __get and __set does some strange things 
when commenting in and out the $this->array = []; line. 
Test the code and you will see. 
This all started with "Typed Properties" and its is very strange.

So, is this normal behavior? If yes, why is that so? 

Must Important Question: 

Why are the magic methods connected to the property definition and the error clearly says "X::$bar must be string, null used"? Because at this point the __get returns NULL. 

On a previous BUG Report (Bug #78226) someone sayed: Do always ?string $foo = null;
But that can be the solution to this, or?

Test script:
---------------
class X
{
    protected string $bar;
    protected array $array = [];
    public function __construct(string $bar)
    {
        $this->bar = $bar; $this->array = []; print $this->bar.PHP_EOL;
    }
    public function __set($name, $value)
    {
        print "set $name = $value".PHP_EOL; $this->array[$name] = $value;
    }
    public function __get($name)
    {
        print 'get '.$name.PHP_EOL; return $this->array[$name] ?? null;
    }
}
$obj = new X('FOO');

Expected result:
----------------
set bar = FOO 
get bar 
FOO

Actual result:
--------------
set bar = FOO 
get bar 

Fatal error: Uncaught TypeError: Typed property X::$bar must be string, null used

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-10-23 16:46 UTC] nikic@php.net
The __get() result value must still satisfy the property type if it is invoked on a visible, but unset/uninitialized property. Otherwise it would be possible to bypass the property type this way.

It should be noted that something like this will not throw an error:

<?php
class X
{
    protected string $bar;
    protected array $array = [];
    public function __set($name, $value)
    {
        print "set $name = $value".PHP_EOL; $this->array[$name] =
$value;
    }
    public function __get($name)
    {
        print 'get '.$name.PHP_EOL; return $this->array[$name] ?? null;
    }
}
$obj = new X();
$obj->bar = 'FOO'; print $obj->bar.PHP_EOL;


The reason is that here the access is performed from a scope where the property is not visible, so the type doesn't apply either. The type only applies if it is visible.

See also https://wiki.php.net/rfc/typed_properties_v2#overloaded_properties.
 [2019-10-23 17:11 UTC] dirk dot gerigk at atraveo dot com
Ok, but why is 
   public string $bar;
or 
   protected string $bar;
not set proper in the first place, regardless of the scope?

Basically this is useless: 
   public string $bar;
because there seems no way to set the property in any way 
if an _get & _set is present.

Test this

class X
{
    protected string $bar;
    public function __construct(){
        $this->bar = 'FOO'; 
        print $this->bar.PHP_EOL;
    }
    public function __set($name, $value)
    {
        $this->{$name} = $value;
    }
}
$obj = new X();

and it works. How strange....
 [2019-10-23 17:19 UTC] dirk dot gerigk at atraveo dot com
Ok, like the last time (last Bug Report). 
Maybe i never will understand this hole thing. 
So i try to work the save road on this topic, to prevent behavior that i don't fully understand. 
I understand now why "X::$bar must be string, null used" happened, and that is one step in the right direction for me. 

So thanks for the fast reply. 

p.s. ignore the 'Basically this is useless:' part from my last post
 [2019-10-23 17:48 UTC] nikic@php.net
-Status: Open +Status: Not a bug
 [2019-10-23 17:48 UTC] nikic@php.net
Yes, the main problem here is still bug #78226, which is yet unsolved.

You *can* set the property inside the __set() handler. Basically something like

public function __set($name, $value) {
    if (property_exists($this, $name)) {
        $this->$name = $value;
        return;
    }
    // normal __set implementation
}

may work.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 14:01:29 2024 UTC