rfc:container-offset-behaviour

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:container-offset-behaviour [2024/07/09 23:29] – Make this a h5 as apparently Dokuwiki does not support h6 headings... girgiasrfc:container-offset-behaviour [2024/07/30 09:47] (current) – Move target version girgias
Line 1: Line 1:
 ====== PHP RFC: Improve language coherence for the behaviour of offsets and containers ====== ====== PHP RFC: Improve language coherence for the behaviour of offsets and containers ======
  
-  * Version: 0.2+  * Version: 0.3
   * Date: 2023-12-07   * Date: 2023-12-07
   * Author: Gina Peter Banyard <girgias@php.net>   * Author: Gina Peter Banyard <girgias@php.net>
   * Status: Under Discussion   * Status: Under Discussion
-  * Target Version: PHP 8.4+  * Target Version: PHP 8.5
   * Implementations:   * Implementations:
-  * New handler API: <https://github.com/Girgias/php-src/pull/19> +    * New handler API: <https://github.com/Girgias/php-src/pull/19> 
-  * <php>ArrayObject</php>: <https://github.com/php/php-src/pull/12037> +    * <php>ArrayObject</php>: <https://github.com/php/php-src/pull/12037> 
-  * Original string offset clean-up PR: <https://github.com/php/php-src/pull/7173>+    * Original string offset clean-up PR: <https://github.com/php/php-src/pull/7173>
   * First Published at: <http://wiki.php.net/rfc/container-offset-behaviour>   * First Published at: <http://wiki.php.net/rfc/container-offset-behaviour>
   * Markdown source: <https://github.com/Girgias/php-rfcs/blob/master/container-offset-behaviour.md>   * Markdown source: <https://github.com/Girgias/php-rfcs/blob/master/container-offset-behaviour.md>
Line 32: Line 32:
 ==== Operations ==== ==== Operations ====
  
-We consider there to be nine (9) different operations that relate to containers and offsets,+We consider there to be ten (10) different operations that relate to containers and offsets,
 which are the following: which are the following:
  
Line 44: Line 44:
   * Fetch   * Fetch
   * Fetch-Append   * Fetch-Append
 +  * Incrementing/Decrementing the offset of a container
  
 The read, write, read-write, appending, and unsetting operations are what one would expect. The read, write, read-write, appending, and unsetting operations are what one would expect.
Line 60: Line 61:
 In general, a nested operation will perform all the necessary fetch/read operations, In general, a nested operation will perform all the necessary fetch/read operations,
 interpreting the returned value as a container, until it reaches the final dimension. interpreting the returned value as a container, until it reaches the final dimension.
 +
 +Finally, we mention incrementing/decrementing because in general it should behave like <php>+= 1</php>,
 +but as it works on a "reference" of the value, the behaviour is slightly tricky.
  
 ==== Container types ==== ==== Container types ====
Line 68: Line 72:
   * <php>false</php>   * <php>false</php>
   * <php>true</php>   * <php>true</php>
-  * <php>bool</php> 
   * <php>int</php>   * <php>int</php>
   * <php>float</php>   * <php>float</php>
Line 120: Line 123:
   * For read operations, <php>null</php> is returned and the following warning is emitted: <code>Warning: Trying to access array offset on TYPE</code>   * For read operations, <php>null</php> is returned and the following warning is emitted: <code>Warning: Trying to access array offset on TYPE</code>
  
-  * For write, read-write, appending, fetch, and fetch-append operations, the following error is thrown:<code>Cannot use a scalar value as an array</code>+  * For write, read-write, appending, fetch, fetch-append, and increment/decrement operations, the following error is thrown:<code>Cannot use a scalar value as an array</code>
  
   * For the unset operation, the following error is thrown:<code>Cannot unset offset in a non-array variable</code>   * For the unset operation, the following error is thrown:<code>Cannot unset offset in a non-array variable</code>
Line 144: Line 147:
   * For write, append, fetch, and fetch-append operations the container is converted to array. And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type. Please see the <php>array</php> section for details.   * For write, append, fetch, and fetch-append operations the container is converted to array. And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type. Please see the <php>array</php> section for details.
  
-  * For read-write operations, the container is converted to array, before the read operation. And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type. Please see the <php>array</php> section for details.+  * For read-write and increment/decrement operations, the container is converted to array, before the read operation. And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type. Please see the <php>array</php> section for details.
  
   * For the unset operation, the container continues to be <php>null</php> and no warning or error is emitted/thrown.   * For the unset operation, the container continues to be <php>null</php> and no warning or error is emitted/thrown.
Line 161: Line 164:
   * For write, append, fetch, and fetch-append operations the container is converted to array, Emitting the following deprecation notice:<code>Deprecated: Automatic conversion of false to array is deprecated</code> And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type. Please see the <php>array</php> section for details.   * For write, append, fetch, and fetch-append operations the container is converted to array, Emitting the following deprecation notice:<code>Deprecated: Automatic conversion of false to array is deprecated</code> And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type. Please see the <php>array</php> section for details.
  
-  * For read-write operations, the container is converted to array, before the read operation,+  * For read-write and increment/decrement operations, the container is converted to array, before the read operation,
   Emitting the following deprecation notice: <code>Deprecated: Automatic conversion of false to array is deprecated</code> And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type.  Please see the <php>array</php> section for details.   Emitting the following deprecation notice: <code>Deprecated: Automatic conversion of false to array is deprecated</code> And thus behave like an <php>array</php>, meaning the behaviour depends on the offset type.  Please see the <php>array</php> section for details.
  
Line 246: Line 249:
  
   * Fetch operations will throw different errors depending on the fetch operation, //after// the type of the offset has been checked:   * Fetch operations will throw different errors depending on the fetch operation, //after// the type of the offset has been checked:
-  * For attempting to retrieve a reference to a string offset: <code>Cannot create references to/from string offsets</code> +    * For attempting to retrieve a reference to a string offset: <code>Cannot create references to/from string offsets</code> 
-  * For attempting to use the string offset as a container: <code>Cannot use string offset as an array</code> +    * For attempting to use the string offset as a container: <code>Cannot use string offset as an array</code> 
-  * For attempting to use the string offset as an object: <code>Cannot use string offset as an object</code> +    * For attempting to use the string offset as an object: <code>Cannot use string offset as an object</code> 
-  * For attempting to use increment or decrement the string offset: <code>Cannot increment/decrement string offsets</code>+    * For attempting to use increment or decrement the string offset: <code>Cannot increment/decrement string offsets</code>
  
 Attempting to read a non initialized string offset emits the following warning: Attempting to read a non initialized string offset emits the following warning:
Line 512: Line 515:
  
   * Fetch Append: the <php>ArrayAccess::offsetGet($offset)</php> method is called with <php>$offset</php> being equal to <php>null</php>   * Fetch Append: the <php>ArrayAccess::offsetGet($offset)</php> method is called with <php>$offset</php> being equal to <php>null</php>
 +
 +  * Increment/Decrement: behaves like a fetch operation
  
 Because <php>ArrayAccess::offsetGet($offset)</php> is called for fetching operations, if it does not return an object or by-reference, Because <php>ArrayAccess::offsetGet($offset)</php> is called for fetching operations, if it does not return an object or by-reference,
Line 605: Line 610:
  
 $o = new T(); $o = new T();
-$a = new ArrayObject(); 
- 
 $a = new ArrayObject($o); $a = new ArrayObject($o);
 var_dump(isset($a['p'])); var_dump(isset($a['p']));
Line 677: Line 680:
  
   * Call method to verify the offset exists:   * Call method to verify the offset exists:
-  * If it does not exist: return <php>false</php> (<php>true</php> for <php>empty()</php>+    * If it does not exist: return <php>false</php> (<php>true</php> for <php>empty()</php>
-  * Otherwise: call method to get value of offset:+    * Otherwise: call method to get value of offset:
     - If the value is <php>null</php> (or falsy for <php>empty()</php>) return <php>false</php> (<php>true</php> for <php>empty()</php>)     - If the value is <php>null</php> (or falsy for <php>empty()</php>) return <php>false</php> (<php>true</php> for <php>empty()</php>)
     - Otherwise: return <php>true</php> (<php>false</php> for <php>empty()</php>)     - Otherwise: return <php>true</php> (<php>false</php> for <php>empty()</php>)
Line 799: Line 802:
 Cross-version compatible code can use DNF types to type their input arguments, e.g: Cross-version compatible code can use DNF types to type their input arguments, e.g:
 <PHP> <PHP>
-function foo(ArrayAccess|(DimensionReadable&DimensionWritable)) {+function foo(ArrayAccess|(DimensionFetchable&DimensionWritable)) {
     /* Do something useful */     /* Do something useful */
 } }
Line 882: Line 885:
 and it cannot fulfill its role anymore with the new dimension handlers, and it cannot fulfill its role anymore with the new dimension handlers,
 the struct is removed and alongside it the pointer to such a struct on the <php>zend_class_entry</php>. the struct is removed and alongside it the pointer to such a struct on the <php>zend_class_entry</php>.
 +
 +== Throw an Error when trying to increment or decrement an object offset ==
 +
 +Incrementing/decrementing an object offset results in fetch operation,
 +whereas using <php>+=</php>/<php>-=</php> uses a Read-Write sequence,
 +and usually when acting on object properties this is what happens.
 +
 +The current limitation is because we do not have sufficient specialized VM opcodes for this specific case.
 +Therefore, we propose to hard error, like we do for string offsets, so that if we do add the relevant opcodes we can
 +properly support the Read-Write behaviour.
  
 == Changes to ArrayObject == == Changes to ArrayObject ==
Line 892: Line 905:
   * Fix <php>null</php> offset handling (following from the proper support of the appending operation)   * Fix <php>null</php> offset handling (following from the proper support of the appending operation)
   * When using an object as a backing value:   * When using an object as a backing value:
-  * Throw <php>Error</php> on appending +    * Throw <php>Error</php> on appending 
-  * Emit dynamic properties warning when using an object as a backing value that does not allow dynamic properties +    * Emit dynamic properties warning when using an object as a backing value that does not allow dynamic properties 
-  * Throw <php>Error</php> on writing to <php>readonly</php> properties +    * Throw <php>Error</php> on writing to <php>readonly</php> properties 
-  * Throw <php>Error</php> on writing a value of the wrong type to a typed property+    * Throw <php>Error</php> on writing a value of the wrong type to a typed property 
 +    * Throw <php>Error</php> when using an <php>int</php> as a property
   * Continue to ignore any <php>__set()</php>/<php>__get()</php> magic methods   * Continue to ignore any <php>__set()</php>/<php>__get()</php> magic methods
  
Line 906: Line 920:
 but it also "supports" appending, fetching, and fetch-appending. but it also "supports" appending, fetching, and fetch-appending.
  
-Our solution is to add legacy dimension handlers to classes that implement <php>ArrayAccess</php> +The solution we came up with is for <php>ArrayAccess</php> to formally extend 
-reproducing the current behaviour for appending, fetching and fetch-appending.+<php>DimensionReadable</php>, <php>DimensioWriteable</php>, and <php>DimensionUnsettable</php>; 
 +but also add legacy dimension handlers reproducing the current behaviour when appending, fetchingand fetch-appending 
 +such an object.
 However, if one of the new interfaces is implemented for dedicated support to appending, fetching, However, if one of the new interfaces is implemented for dedicated support to appending, fetching,
 and fetch-appending, then the new behaviour is used. and fetch-appending, then the new behaviour is used.
Line 953: Line 969:
 The <php>array_key_exists()</php> function, and any objects mimicking array offsets, is also affected and would have the <php>resource</php> The <php>array_key_exists()</php> function, and any objects mimicking array offsets, is also affected and would have the <php>resource</php>
 type removed from the union type for the <php>$key</php> parameter. type removed from the union type for the <php>$key</php> parameter.
 +
 +Moreover, most code that expects resources as offsets already use an explicit <php>(int)</php> cast to suppress the warnings.
  
 == Emit warnings for invalid offset types on arrays == == Emit warnings for invalid offset types on arrays ==
Line 1034: Line 1052:
  
 Promote all warnings to <php>Error</php> Promote all warnings to <php>Error</php>
 +
 +===== Backward Incompatible Changes =====
 +
 +A recap of the BC breaking changes being introduced in PHP 8.4:
 +
 +  * New algorithm when calling <php>isset()</php>
 +  * <php>resource</php> as an offset type for arrays would throw an <php>Error</php> as of PHP 8.4
 +  * Trying to read offsets of a <php>MultipleIterator</php> object would throw an <php>Error</php> as of PHP 8.4
 +  * Leading numeric string used as an offset for strings would throw an <php>Error</php> as of PHP 8.4
 +  * Float numeric strings (i.e. non integer-numeric strings) used as an offset for strings would now throw an <php>Error</php> with the null coalesce operator <php>??</php> (in line with <php>isset()</php>)
 +  * <php>ArrayObject</php> would behave more sensibly and in line with every other PHP object, rather than being weird
 +
 +A recap of the new warnings being introduced in PHP 8.4:
 +
 +  * A warning when checking the existence of an offset on invalid container types (except for <php>null</php>)
 +  * A warning prior to reading an undefined offset when it is part of a read-write operation
 +  * A warning is emitted when trying to use a value of type <php>null</php>, <php>bool</php>, or <php>float</php> as an array offset
 +  * A warning is emitted when trying to check the existence of string offset with invalid offset types
 +
 +A recap of the deprecations being introduced in PHP 8.4:
 +
 +  * The <php>SplObjectStorage::contains()</php> method is deprecated in favour of <php>SplObjectStorage::offsetExists()</php>
 +  * The <php>SplObjectStorage::detatch()</php> method is deprecated in favour of <php>SplObjectStorage::offsetUnset()</php>
 +  * The <php>SplObjectStorage::attach()</php> method is deprecated in favour of <php>SplObjectStorage::offsetSet()</php>
 +
 +For details on each of those, refer to their relevant sections.
  
 ===== Version ===== ===== Version =====
rfc/container-offset-behaviour.1720567795.txt.gz · Last modified: 2024/07/09 23:29 by girgias