rfc:object_cast_magic
no way to compare when less than two revisions

Differences

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


Previous revision
Next revision
rfc:object_cast_magic [2012/03/04 01:02] – change title ircmaxell
Line 1: Line 1:
 +===== Request for Comments: Object Cast and Assign Magic Methods =====
 +  * Version: 0.1
 +  * Date: 2012-02-28
 +  * Author: Anthony Ferrara <ircmaxell@php.net>
 +  * Status: In Draft
 +
 +===== Introduction =====
 +
 +Currently, the Zend Engine exposes several methods to PECL extensions and internal classes to control how internal objects behave when casting to primitives, and when assigning to the variable that used to hold the object.  The three methods are specifically //cast_object()//, //get()// and //set()// This proposal is to expose those three methods as two magic methods to allow user classes to hook into this functionality.
 +
 +It should be noted, that this requires no engine changes to implement.  All it is doing is exposing three engine handlers that are already existing and functioning for internal classes to userland classes via magic methods.  Therefore, there really is no new significant functionality, other than allowing userland code to implement the handlers.
 +===== Use Cases =====
 +
 +==== Scalar Wrapping ====
 +
 +One use-case for this functionality is in creating wrapper classes for scalar values.  There are several possibilities that this will enable:
 +
 +   * Creating strict-typing functionality with entirely user-land code (having a variable that until unset cannot be assigned to with anything except an integer for example)
 +   * Having object code be compatible with existing scalar-typed code.  An example would be SplFixedArray would be enabled to be passed to any function (including internal functions) that expect arrays.
 +   * Creating new semi-primitive types with functional limitations applied.  An example would be the ability to create an integer type that is always less than a configured maximum.  Another example would be to create a string type that self-prevents invalid characters (by regex, or otherwise)...
 +   * Creating constant (const) variables that can be written to once (on creation), but not again.  (this is sort-of not true, since you can still manually unset() the variable, but it would prevent accidental overwriting)
 +
 +==== Boolean Logic ====
 +
 +Right now, when you use an object in a boolean context, it always evaluates to //TRUE// By overloading the castTo with boolean type, you can have the object return as true or false.  This can be used for implementing validators that can be checked with a simple //if ($valid)// instead of having to proxy to a method.
 +
 +==== Providing Backwards Compatibility ====
 +
 +Quite a few systems depend on primitives to represent data structures.  One example is Drupal's form system that uses multi-dimensional arrays to pass data around.  This casting functionality would allow an object system with validation to be used to represent the same structure, and when combined with ArrayAccess and Iterator can be used to provide a backwards-compatible API change.  The existing array structures would still be exposed if the code uses array functions or assigns an array to the object.  But the new functionality would be exposed since the data is still in a literal object.
 +
 +===== Proposal and Patch =====
 +
 +This RFC proposes two new magic methods to be called when the internal object handlers are invoked:
 +
 +    mixed __castTo(string $type)
 +    bool  __assign(mixed $newValue)
 +
 +==== Handler Behavior ====
 +
 +When the cast_object handler is called, the //castTo// method is invoked with a string representation of the type to be casted to.  The return value is then used as the "casted" value.  This would be called when explicitly cast (using //(int)// style syntax), when implicitly cast (passing as a parameter to an internal function expecting a specific type parameter) and when casted to a string (echo $obj;). 
 +
 +When the get handler is called, the //castTo// method is invoked with the parameter "//native//" This would indicate that the cast is to a scalar, but the type is up to the class to determine.  This is called when a primitive type is needed, but not a specific one.  So the normal math operators trigger a get call: (+, -, *, /), in addition to compound operators (++, +=, &=, |=, etc).
 +
 +When the set handler is called, the //assign// method is invoked with the parameter of the new value.  The object can then react to this assignment (by setting a new internal state, by throwing an exception, etc).  If the object wants to be overwritten, it can return //FALSE//, which will cause the default assignment handler (overwriting the object) to happen.
 +
 +==== Example Triggers ====
 +
 +  <?php
 +  
 +   $obj++; // triggers __castTo("native"), followed by __assign($result_from_cast + 1)
 +   $obj = $obj + 1; // same as above
 +   $obj |= 5; // triggers __castTo("native"), followed by __assign($result_from_cast | 5)
 +      
 +   $obj = 5; // triggers __assign(5)
 +   $obj = array(1, 2, 3); // triggers __assign(array(1, 2, 3))
 +   $obj = new StdClass; // triggers __assign(new StdClass)
 +
 +   if ($obj) // triggers __castTo("boolean")
 +   
 +
 +A proof of concept patch is available here: [[https://gist.github.com/1929587]] (note, this is a POC **ONLY**, there are issues with it, such as memory leaks).  
 +
 +==== __toString ====
 +
 +The //toString// magic method would become redundant, since it is just a limited version of the //castTo// handler (it actually implements the cast_object handler internally).  Based on that, any class implementing //castTo// would then ignore a //toString// method if it's defined.  For backwards compatibility reasons, if //castTo// is **not** defined on the class (or parent), then //toString// would be executed for any string casts.
 +
 +Eventually, //toString// would be deprecated as duplicate functionality (but not any time soon, as it would still work fine).
 +
 +==== Example ==== 
 +
 +  <?php
 +  
 +  class MyInteger {
 +      protected $value;
 +      public function __construct($value = 1) {
 +          $this->value = (int) $value;
 +      }
 +      public function __castTo($type) {
 +          switch ($type) {
 +              case 'integer':
 +              case 'native':
 +                  return $this->value;
 +              case 'float':
 +                  return (float) $this->value;
 +              case 'string':
 +                  return (string) $this->value;
 +              default:
 +                  throw new LogicException('Cannot Cast Integer To ' . $type);
 +          }
 +      }
 +      public function __assign($value) {
 +          if (is_numeric($value)) {
 +              $this->value = (int) $value;
 +              return;
 +          }
 +          return false;
 +      }
 +  }
 +  
 +  $foo = new MyInteger(1);
 +  $foo = 2 + 2;
 +  // $foo is still an object, with $value = 4
 +  $foo++;
 +  // $foo is still an object, with $value = 5
 +  $foo = floor($foo);
 +  // $foo is still an object, with $value = 5 (since floor causes a cast to float, and then assignment causes a cast back to int)
 +  $foo = array();
 +  // $foo is now an array, since is_numeric fails
 +
 +===== Further reading =====
 +
 +  * https://wiki.php.net/rfc/class_casting_to_scalar
 +  * https://wiki.php.net/internals/engine/objects#get
 +  * https://wiki.php.net/internals/engine/objects#set
 +  * https://wiki.php.net/internals/engine/objects#cast_object
 +
 +===== Questions =====
 +How would this RFC affect:
 +
 +resources
 +
 +streams
 +
 +filters
 +
 +...
  
rfc/object_cast_magic.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1