rfc:deprecate_dynamic_properties

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:deprecate_dynamic_properties [2021/08/25 13:41] nikicrfc:deprecate_dynamic_properties [2021/11/26 13:59] (current) nikic
Line 2: Line 2:
   * Date: 2021-08-23   * Date: 2021-08-23
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Status: Under Discussion+  * Status: Implemented
   * Target Version: PHP 8.2   * Target Version: PHP 8.2
-  * Implementation: https://github.com/php/php-src/pull/7390+  * Implementation: https://github.com/php/php-src/pull/7571
  
 ===== Introduction ===== ===== Introduction =====
  
-When writing to a property that has not been declared, PHP will silently create a dynamic property instead. In modern code, this is rarely done intentionally. This RFC proposes to deprecate and later remove the creation of dynamic properties. stdClass and %%__get%%/%%__set%% are not affected by this change.+When writing to a property that has not been declared, PHP will silently create a dynamic property instead. In modern code, this is rarely done intentionally. This RFC proposes to deprecate and later remove the creation of dynamic properties, unless the class explicitly allows dynamic properties. stdClass and %%__get%%/%%__set%% are not affected by this change.
  
 <PHP> <PHP>
Line 28: Line 28:
  
 "Dynamic property" here refers to a property that has not been declared in the class. It has no relation to the access style (e.g. ''$user->{'na' . 'me'}'' is still an access to a declared property). "Dynamic property" here refers to a property that has not been declared in the class. It has no relation to the access style (e.g. ''$user->{'na' . 'me'}'' is still an access to a declared property).
 +
 +==== Motivation ====
 +
 +The motivation for this change is twofold: To prevent mistakes (due to typos or renames) in the common case, and to make intentional uses explicit. The core problem is that reading from a non-existing property issues a diagnostic that makes the issue immediately apparent, while writing to a non-existing property is entirely silent. PHP gives no indication whatsoever that the programmer has made a mistake.
 +
 +A common counter-argument is that even if PHP itself does not detect the mistake, static analysis still can. While this is true to a degree, there are a number of problems:
 +
 +  - Static analysis in IDEs (probably the most widespread type of static analysis used in PHP) has to be conservative about diagnostics relating to dynamic properties. For example, PhpStorm treats creation of dynamic properties as only a weak warning (non-intrusive grey underline), because it cannot distinguish whether this dynamic property assignment is indeed a bug, or an intentional use. Treating dynamic property creation as a more severe error would result in false positives in cases where dynamic properties are used intentionally. The ''#[AllowDynamicProperties]'' attribute proposed in this RFC makes the cases where dynamic properties are used intentionally explicit. This means that static analysis tooling can both a) suppress any diagnostic in explicitly allowed cases and b) report a hard error in all other cases, without producing any false positives.
 +  - Static analysis can only analyze assignments with a known object type and property name. This does not include any kind of dynamic assignments, such as those that may be performed by a serializer, hydrator or any other mechanism that directly populates objects without going through a constructor. Static analysis also requires a fully-typed codebase, as the actually assigned property may otherwise not be known.
 +  - On a more philosophical note, I believe that a programming language should be usable without external tooling. While IDE use is widespread among PHP programmers, it should still be possible to write PHP code in a text editor like vim without exposing you to silent bug classes.
 +
 +Finally, if classes using dynamic properties are explicitly declared, then we no longer need to reserve space for them on each object. This would reduce the size of all objects (that don't opt-in to dynamic properties) by 8 bytes. However, this is a fairly long-term benefit that will require additional technical work to realize.
  
 ===== Proposal ===== ===== Proposal =====
  
-The creation of dynamic properties on classes that don'inherit from ''stdClass'' is deprecated in PHP 8.2 and becomes an Error exception in PHP 9.0. All used properties should be declared in the class declaration.+The creation of dynamic properties on classes that aren'marked with the ''#[AllowDynamicProperties]'' attribute is deprecated in PHP 8.2 and becomes an Error exception in PHP 9.0. All used properties should be declared in the class declaration.
  
 <PHP> <PHP>
Line 44: Line 56:
 </PHP> </PHP>
  
-Objects of type ''stdClass'' and inheriting classes continue to support dynamic properties.+Classes marked with ''#[AllowDynamicProperties]'' as well as their children can continue using dynamic properties without deprecation or removal. The only bundled class marked as ''#[AllowDynamicProperties]'' is ''stdClass''.
  
 <PHP> <PHP>
Line 52: Line 64:
 $obj->foo = 1; $obj->foo = 1;
  
-class myStdClass extends stdClass {} +#[AllowDynamicProperties] 
-$obj2 = new myStdClass;+class Test {} 
 +class Test2 extends Test {}
  
 // No deprecation warning // No deprecation warning
-$obj2->bar = 1;+$obj = new Test; 
 +$obj->bar = 1; 
 + 
 +// No deprecation warning 
 +$obj = new Test2; 
 +$obj->bar = 1;
 </PHP> </PHP>
- 
-''stdClass'' objects are specifically intended to hold dynamic properties. ''extends stdClass'' is offered as a simple migration strategy for custom classes that are also specifically intended for use with dynamic properties. 
  
 It should be noted that properties accessed through ''%%__get()%%''/''%%__set()%%'' are not considered as "dynamic properties". The following example does not generate any deprecation warnings: It should be noted that properties accessed through ''%%__get()%%''/''%%__set()%%'' are not considered as "dynamic properties". The following example does not generate any deprecation warnings:
Line 94: Line 110:
 </PHP> </PHP>
  
-For classes that intentionally don't have a fixed set of properties, it's possible to either implement magic ''%%__get()%%''/''%%__set()%%''or to extend from the ''stdClass'' class, or from ''ArrayObject'' in ''ARRAY_AS_PROPS'' mode.  +For classes that intentionally don't have a fixed set of properties, it's possible to either implement magic ''%%__get()%%''/''%%__set()%%'' or mark the class using the ''#[AllowDynamicProperties]'' attribute. Marking a class with ''#[AllowDynamicProperties]'' is fully backwards-compatible with earlier PHP versionsbecause prior to PHP 8.0 this would be interpreted as a commentand the use non-existent classes as attributes is not an error.
- +
-Using magic getters/setters provides the most controlbut extending from ''stdClass'' will make dynamic property accesses more efficient by using optimized engine hooksIt will also match the current behavior most closelyfor example with regard to the behavior of ''foreach'' or ''property_exists()''.+
  
 In some cases it is desirable to associate information with objects that you do not own. Previously, it was possible to add a dynamic property for this purpose. Instead, a ''WeakMap'' should be used to store the information in a non-intrusive way: In some cases it is desirable to associate information with objects that you do not own. Previously, it was possible to add a dynamic property for this purpose. Instead, a ''WeakMap'' should be used to store the information in a non-intrusive way:
Line 136: Line 150:
 ==== Alternative opt-in to dynamic properties ==== ==== Alternative opt-in to dynamic properties ====
  
-This RFC offers ''extends stdClass'' as a way to opt-in to the use of dynamic properties. Some people have suggested that we could use a magic marker interface (''implements SupportsDynamicProperties'')an attribute (''#[SupportsDynamicProperties]'') or a trait (''use DynamicProperties;'') instead.+This RFC offers ''#[AllowDynamicProperties]'' as a way to opt-in to the use of dynamic properties. A previous version of this proposal instead suggested to extend from ''stdClass'', and make ''stdClass'' the only class with first-class dynamic property support.
  
-The reasoning behind the ''extends stdClass'' choice is that it works without any additional special support: We definitely need to allow dynamic properties on ''stdClass'', and following the Liskov substitution principle, child classes should inherit this behavior. As such, the ''extends stdClass'' escape hatch will work anyway, and the question is more whether we want to offer anything in addition to it.+The difference between these approaches is in the end goal: ''#[AllowDynamicProperties]'' requires making classes that rely on dynamic properties explicit and prevents accidental use of dynamic properties. This is a big win for the ecosystem, but it does not have much effect on the overall complexity of the language or implementation, as dynamic properties still need to be supported on arbitrary classes. Requiring an extension of ''stdClass'' would allow us to actually remove the "dynamic properties" concept from the language in the future: ''stdClass'' would effectively just provide very optimized implementations of ''%%__get()%%'' and ''%%__set()%%''.
  
-Another way to view this is that ''stdClass'' could implement ''%%__get()%%''/''%%__set()%%'' to provide its "dynamic properties" support, in which case these methods would naturally be inheritedOf courseit currently doesn't do so, but it probably should once dynamic property support is removed.+While completely removing dynamic properties is a worthwhile end goal, we also need to acknowledge that dynamic properties have played an important historical role in PHP, and legacy codebases in particular may be making heavy use of them. While adding an attribute provides a straightforward upgrade path, extending ''stdClass'' may not always be easily possible due to lack of multiple inheritance support. For this reasonthis RFC pursues the more conservative attribute-based approach.
  
-Using an interface or attribute instead would require the engine to continue supporting dynamic properties on arbitrary classes long term, rather than simply inheriting the behavior from a single class that implements the functionality. +We may still wish to remove dynamic properties entirely at some later point. Having the ''#[AllowDynamicProperties]'' attribute will make it much easier to evaluate such moveas it will be easier to analyze how much and in what way dynamic properties are used in the ecosystem.
- +
-A trait based on ''%%__get()%%''/''%%__set()%%'' could be provided, and would be usable in multiple-inheritance cases where extending from stdClass is not possible: +
- +
-<PHP> +
-class DynamicProperties { +
-    private array $dynamicProps = []+
-    public function &__get($name) { return $this->dynamicProps[$name];+
-    public function __isset($name, $value) { return isset($this->dynamicProps[$name];+
-    public function __set($name, $value) { $this->dynamicProps[$name] = $value; } +
-    public function __unset($name) { unset($this->dynamicProps[$name];+
-+
-</PHP> +
- +
-However, such a trait would not differ from simply implementing these methods in userland. Unlike ''extends stdClass'', it would not benefit from optimized internal hooks, and it would not be able to offer exactly the same functionality. A custom implementation does not take significant amount of codebut has more control over what exactly it wants to support. For example, next to the above baseline implementation, a class might also want to implement ''%%__debugInfo()%%'' and the ''Traversable'' interface.+
  
 ==== Opt-out of dynamic properties instead ==== ==== Opt-out of dynamic properties instead ====
Line 168: Line 168:
 However, based on the discussion on the language evolution proposal, this would only delay the time where disallowed dynamic properties become the default and only behavior, as there was a strong consensus that diverging language behavior should not be maintained indefinitely. Dynamic properties would ultimately still get deprecated and removed. However, based on the discussion on the language evolution proposal, this would only delay the time where disallowed dynamic properties become the default and only behavior, as there was a strong consensus that diverging language behavior should not be maintained indefinitely. Dynamic properties would ultimately still get deprecated and removed.
  
-==== Internal impact ====+===== Vote =====
  
-Internal classes can already specify the ''ZEND_ACC_NO_DYNAMIC_PROPERTIES'' flag to disable dynamic property creation. During the deprecation phase, this RFC is non-intrusive and only adds a ''ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES'' flag to efficiently opt-out stdClass from the deprecation warning.  +Voting started 2021-11-12 and ended 2021-11-26.
- +
-Once dynamic properties are disallowed, some larger changes should be made. Support for dynamic properties should be dropped from the virtual machine and default object handlers. Instead ''stdClass'' should implement custom object handlers, possibly in conjunction with ''%%__get()%%''/''%%__set()%%'', ''%%__debugInfo()%%'' and ''Traversable'' to present the right userland interface. +
- +
-Objects should no longer store a ''properties'' member, reducing the size of all objects by 8 bytes. The ''get_properties()'' object handler should be dropped. Instead code inspecting all object properties should loop over the ''properties_table'' and use ''properties_info_table'' to map property slots back to their metadata. For example, ''foreach'' over an object would no longer materialize the dynamic properties table (which remains after the loop and dramatically increases the object size) and instead efficiently iterate the property slots. +
- +
-===== Vote =====+
  
-Yes/No.+<doodle title="Deprecate dynamic property creation with #[AllowDynamicProperties] opt-in?" auth="nikic" voteType="single" closed="true"> 
 +   Yes 
 +   No 
 +</doodle>
rfc/deprecate_dynamic_properties.1629898876.txt.gz · Last modified: 2021/08/25 13:41 by nikic