php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #72194 unset() on declared class properties undeclares it partially
Submitted: 2016-05-11 11:11 UTC Modified: 2021-11-23 17:13 UTC
Votes:3
Avg. Score:5.0 ± 0.0
Reproduced:2 of 2 (100.0%)
Same Version:1 (50.0%)
Same OS:2 (100.0%)
From: pierre dot rineau at makina-corpus dot com Assigned: cmb (profile)
Status: Wont fix Package: Reflection related
PHP Version: Irrelevant OS: Any
Private report: No CVE-ID: None
 [2016-05-11 11:11 UTC] pierre dot rineau at makina-corpus dot com
Description:
------------
If you declare a class with declared properties, you might want to use unset() to reset the property value to null, but when accessing it later, it causes a "undefined property" warning.

This seems legit, but it becomes tricky once you notice that property_exists() returns true before calling unset(), which is the wanted behavior since the property is declared (even when uninitialized), but it also returns true after calling unset(), which is incompatible with the "undefined property" warning.

It seems to always have worked this way, but any code using property_exists() will be tricked by the false positive.


[someuser@localhost] ~/current
 >  php -v
PHP 5.5.25 (cli) (built: May 14 2015 10:02:12) 
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies
    with Zend OPcache v7.0.4-dev, Copyright (c) 1999-2015, by Zend Technologies
    with Xdebug v2.4.0RC2, Copyright (c) 2002-2015, by Derick Rethans
[someuser@localhost] ~/current
 >  php -a
Interactive shell

php > class A { public $b; };
php > $a = new A;
php > echo property_exists($a, 'b');
1
php > $a->b;
php > unset($a->b);
php > echo property_exists($a, 'b');
1
php > $a->b;
PHP Notice:  Undefined property: A::$b in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0
php > 




pounard@guinevere] ~
 >  php -v
PHP 7.0.6 (cli) (built: May  1 2016 07:39:38) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
    with Xdebug v2.4.0, Copyright (c) 2002-2016, by Derick Rethans
[pounard@guinevere] ~
 >  php -a
PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/lib/php/modules/redis.so' - /usr/lib/php/modules/redis.so: undefined symbol: zval_used_for_init in Unknown on line 0
Interactive shell

php > class A { public $b; };
php > $a = new A;
php > echo property_exists($a, 'b');
1
php > $a->b;
php > unset($a->b);
php > echo property_exists($a, 'b');
1
php > $a->b;
PHP Notice:  Undefined property: A::$b in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0
php > 



Test script:
---------------
class A { public $b; };
$a = new A;

echo property_exists($a, 'b'); // Returns '1'
$a->b; // Remains silent

unset($a->b);

echo property_exists($a, 'b'); // Still returns '1'
$a->b; // Cause a "Notice: Undefined property A::$b"


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-05-11 11:12 UTC] pierre dot rineau at makina-corpus dot com
-Summary: unset() on declared class properties triggers warnings when accessing it later +Summary: unset() on declared class properties undeclares it partially
 [2016-05-11 11:12 UTC] pierre dot rineau at makina-corpus dot com
Changed title.
 [2016-05-11 11:16 UTC] pierre dot rineau at makina-corpus dot com
https://3v4l.org/3sAs5
 [2016-05-11 13:58 UTC] nikic@php.net
To clarify, the "bug" you are reporting here is that property_exists() returns true for properties that have been unset?
 [2016-05-12 13:19 UTC] pierre dot rineau at makina-corpus dot com
Yes, it is either one of:
 - property_exists() should not return true for unset() properties
 - unset() un-declares statically declared class properties
 [2016-05-15 14:46 UTC] bwoebi@php.net
The property (as in property declaration) still exists. Only dynamic properties can completely vanish.
The correct construct checking for existence of the property *value* is isset().

Note that unset() sets the value to undefined [not null!], but does not affect the existence of the property declaration if it is a declared one.

class A { public $b; }
$a = new A;
var_dump(property_exists($a, "b")); // true
var_dump(isset($a->b)); // false (as $a->b is null)
var_dump($a->b); // null
var_dump(property_exists($a, "c")); // false
$a->c = 1;
var_dump(property_exists($a, "c")); // true
unset($a->c);
var_dump(property_exists($a, "c")); // false
unset($a->b);
var_dump(property_exists($a, "b")); // true
var_dump(isset($a->b)); // false (as $a->b is undefined)
var_dump($a->b); // null and emits a notice ($a->b is undefined)

It's true that you can trick code using it (wrongly); that code probably should be using isset(). property_exists() is really only meant to check the existence of declaration or it actually having a value.
 [2016-05-25 12:25 UTC] pierre dot rineau at makina-corpus dot com
If you say that the unset()'d property becomes "undefined" and not null, then there is a logical problem: as a developer, there is absolutely no way writing PHP code to determine the difference between "undefined" and "null", then I cannot make my code more robust.

In my opinion, this very edge case behaviour is a bug.
 [2017-07-12 13:55 UTC] sean at m2mobi dot com
This issue currently prevents me from using unittests for cleanup functions.
I can't check if a protected class property has been unset because Reflection reports it as an "undefined property" afterwards.
If it was null before however isset() will report FALSE before and after the unset and property_exists() will report TRUE before and after.

This means I have no way to check if my unset happened, therefore I see this behaviour as a bug in reflection as the properties still exist.
 [2017-09-28 14:51 UTC] cmb@php.net
Related to bug #64497.
 [2021-11-23 17:13 UTC] cmb@php.net
-Status: Open +Status: Wont fix -Assigned To: +Assigned To: cmb
 [2021-11-23 17:13 UTC] cmb@php.net
Bug #64497 has been closed as WONTFIX, so accordingly I'm closing this
ticket as WONTFIX, too.

Note that you can use ReflectionProperty::isInitialized() instead
of property_exists() with the intended behavior[1].

[1] <https://3v4l.org/Ipqhf>
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Dec 26 13:01:30 2024 UTC