Skip to content

Instantly share code, notes, and snippets.

@chobie
Last active December 30, 2015 21:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chobie/7890899 to your computer and use it in GitHub Desktop.
Save chobie/7890899 to your computer and use it in GitHub Desktop.
Add common way to convert array from object for PHP

ArraySerializable Interface

Current Status

Considering

Introduction

this article proposes to add ArraySerializable interface and to allow the changing of existing array conversion mechanism. As an example of this change, consider the following code-snippet:

<?php

class Person implements ArraySerializable
{
  public $name;
  public $phone;

  public function __toArray()
  {
     return array("name" => $this->name, "phone" => $phone);
  }
}

class Phone implements ArraySerializable
{
  public $number;
  public function __toArray()
  {
     return array("number" => $this->number);
  }
}

$person = new Person();
$person->name = "John";
$phone = new Phone();
$phone->number = "123456789";
$person->phone = $phone;

var_dump((array)$person);
# will outputs
#array(2) {
#  ["name"]=>
#  string(4) "John"
#  ["phone"]=>
#  object(Phone)#2 (1) { // oops, does not work recursively.
#    ["number"]=>
#    string(9) "123456789"
#  }
#}

Currently, the above implicit object to array conversion does not work recursively. This propose changes object to array conversion behaviour which implements ArraySerializable interface. specifically ArraySerializable::__toArray method overrides current (array) cast.

<?php

interface ArraySerializable
{
    /** @return array */
    public function __toArray();
}

Motivation

Issues with using JsonSerialize

Issues with common way to convert array from object.

Usually, We serialise PHP object to some portable formats. At such a time as this. we define toArray() method which decides what values are necessary to convert to array.

    public function toArray()
    {
        $result = array();
        foreach ($this as $key => $value) {
            if (in_array($key, $this->whitelist()) {
                if (is_object($value)) {
                  $result[$key] = $value->toArray();
                } else {
                  $result[$key] = $value;
                }
            }
        }

        return $result;
    }

We don't have common rule to convert to array from object.

Solution: ArraySerializable

ArraySerializable interface provides common way to convert to array from object. also impose conversion rule.

  • __toArray() returning value excepts an array and It values only accepts primitive type. (long, double, string and array)
    • do cast to array operation when the value contains object which implements ArraySerializable interface
    • otherwise, raise RuntimeException.
  • __toArray() method calls implicitly when cast to array from object.

NOTE

  • __toArray expects array cast operator. does not work correctly when call __toArray() method manually.
    • specifically, children implicit conversion doesn't work. also it doesn't throw exception.

Patch

rough concept patch (for PHP_5.6 branch) is here: maybe i'll add ArraySerializable interface under Zend directory.

diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c
index e862929..ab7d6a3 100644
--- a/Zend/zend_operators.c
+++ b/Zend/zend_operators.c
@@ -28,8 +28,10 @@
#include "zend_list.h"
#include "zend_API.h"
#include "zend_strtod.h"
+#include "zend_interfaces.h"
#include "zend_exceptions.h"
#include "zend_closures.h"
+#include "ext/spl/spl_array.h"
#if ZEND_USE_TOLOWER_L
#include <locale.h>
@@ -706,6 +708,33 @@ ZEND_API void convert_to_array(zval *op) /* {{{ */
FREE_HASHTABLE(ht);
return;
}
+ } else if (instanceof_function_ex(Z_OBJCE_P(op), spl_ce_ArraySerializable, 1 TSRMLS_CC)) {
+ zval *res, **element;
+
+ Z_ADDREF_P(op);
+ zend_call_method_with_0_params(&op, Z_OBJCE_P(op), NULL, "__toarray", &res);
+ if (Z_TYPE_P(res) == IS_ARRAY) {
+ HashPosition pos;
+ for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(res), &pos);
+ zend_hash_get_current_data_ex(Z_ARRVAL_P(res), (void **)&element, &pos) == SUCCESS;
+ zend_hash_move_forward_ex(Z_ARRVAL_P(res), &pos)) {
+
+ if (Z_TYPE_PP(element) == IS_OBJECT) {
+ Z_DELREF_PP(element); // make both ends meet
+ convert_to_array(*element);
+ if (Z_TYPE_PP(element) != IS_ARRAY) {
+ fprintf(stderr, "OMG");
+ }
+ }
+ }
+
+ zend_hash_destroy(ht);
+ FREE_HASHTABLE(ht);
+
+ zval_dtor(op);
+ ZVAL_ZVAL(op, res, 1, 1);
+ return;
+ }
} else if (Z_OBJ_HT_P(op)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op TSRMLS_CC);
if (obj_ht) {
diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c
index ca8076a..99de02c 100644
--- a/ext/spl/spl_array.c
+++ b/ext/spl/spl_array.c
@@ -42,6 +42,7 @@ PHPAPI zend_class_entry *spl_ce_ArrayObject;
zend_object_handlers spl_handler_ArrayIterator;
PHPAPI zend_class_entry *spl_ce_ArrayIterator;
+PHPAPI zend_class_entry *spl_ce_ArraySerializable;
PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator;
#define SPL_ARRAY_STD_PROP_LIST 0x00000001
@@ -1919,6 +1920,11 @@ static const zend_function_entry spl_funcs_RecursiveArrayIterator[] = {
SPL_ME(Array, getChildren, arginfo_array_void, ZEND_ACC_PUBLIC)
PHP_FE_END
};
+
+static const zend_function_entry spl_funcs_ArraySerializable[] = {
+ SPL_ABSTRACT_ME(ArraySerializable, __toArray, NULL)
+ PHP_FE_END
+};
/* }}} */
/* {{{ PHP_MINIT_FUNCTION(spl_array) */
@@ -1961,6 +1967,8 @@ PHP_MINIT_FUNCTION(spl_array)
REGISTER_SPL_IMPLEMENTS(RecursiveArrayIterator, RecursiveIterator);
spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator;
+ REGISTER_SPL_INTERFACE(ArraySerializable);
+
REGISTER_SPL_CLASS_CONST_LONG(ArrayObject, "STD_PROP_LIST", SPL_ARRAY_STD_PROP_LIST);
REGISTER_SPL_CLASS_CONST_LONG(ArrayObject, "ARRAY_AS_PROPS", SPL_ARRAY_ARRAY_AS_PROPS);
diff --git a/ext/spl/spl_array.h b/ext/spl/spl_array.h
index 0423d89..5c0ae6f 100644
--- a/ext/spl/spl_array.h
+++ b/ext/spl/spl_array.h
@@ -27,6 +27,7 @@
extern PHPAPI zend_class_entry *spl_ce_ArrayObject;
extern PHPAPI zend_class_entry *spl_ce_ArrayIterator;
+extern PHPAPI zend_class_entry *spl_ce_ArraySerializable;
extern PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator;
PHP_MINIT_FUNCTION(spl_array);
<?php
class Person implements ArraySerializable, JsonSerializable{
public $name;
public $phone;
public function __toArray(){
return array(
"name"=>$this->name,
// NOTE: ArraySerializable will implicit call __toArray method. otherwise throwing an exception.
"phone"=>$this->phone,
);
}
public function jsonSerialize()
{
return (array)$this; // this is very easy.
}
}
class Phone implements ArraySerializable{
public $number;
public function __toArray()
{
return array("number"=>$this->number);
}
}
$p=new Person();
$p->name="John";
$ph=new Phone();
$ph->number = "123456";
$p->phone = $ph;
$x = (array)$p; // call `__toArray()` method internally.
var_dump($x);
echo json_encode($p);
/* will outputs
array(2) {
["name"]=>
string(4) "John"
["phone"]=>
array(1) {
["number"]=>
string(6) "123456"
}
}
{"name":"John","phone":{"number":"123456"}}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment