The new PHPJS_Array object

10 views
Skip to first unread message

Brett Zamir

unread,
Mar 26, 2011, 3:15:59 AM3/26/11
to ph...@googlegroups.com
Hi all,

I wanted to announce that I've committed some initial work on
PHPJS_Array() a class currently being generated by array() when an ini
is set (I can make it as a separate file and require it as a dependency
in order to allow direct usage, but that would be making a non-standard
name in the "global" namespace.) Many more methods must be implemented,
but what is there should be working.

It allows the following features:

1) Browsers (at least Chrome and I understand IE9 possibly as well) are
abandoning the mostly predictable for-in iteration ordering of IE and
Firefox: e.g., see http://code.google.com/p/v8/issues/detail?id=164 .
True, the standard has always said the order was
implementation-dependent (and IE was goofy with ordering of
deleted-and-readded properties), but as can be seen in the passion of
the V8 thread, no shortage of people feel it SHOULD require for-in
iteration to follow the order of addition, and beginners tend to assume
it should behave this way as well (since it has and still does in the
still prominent browsers). In any case, with browsers like Chrome
abandoning them, and it being unclear whether new versions of ECMAScript
may ever impose a predictable ordering, the fact is that in order to
work in Chrome today, we CANNOT rely on for-in iteration order, and thus
our current treatment of objects as associative arrays will not work if
the PHP behavior of predictable ordering is to be replicated completely.
The easiest API I have seen suggested where the key is visually paired
with the value, and yet order can be preserved, is the following:

// We set an ini setting to force array() to accept a sequence of
single-property objects
// and treat this internally as though these pairs were just
properties on a single obect.
ini_set('phpjs.return_phpjs_arrays', 'on');
var arr = array({key1: 'a'}, {key2:'b'});

We could also conceivably support the following, but I think they are
all less than ideal:

// Less "associative", but not too bad:
var arr = array(['key1', 'a'], ['key2', 'b']);

// Perhaps clearer about what is going on, but far more cumbersome:
var arr = array({key: 'key1', value: 'a'}, {key: 'key2', value: 'b'});

// Prefix with a number which, upon initial internal iteration can be
used for sequencing--ugly and cumbersome:
var arr = array({$0_key1: 'a', $1_key2: 'b'});

2) Chaining.

Since we need to make our own wrapper for this functionality anyways, I
figured we may as well offer some other logical features and do things
more the "JavaScript way", for example, chaining methods:

ini_set('phpjs.return_phpjs_arrays', 'on');
var arr = array({k1: 'a'}, {k2:'b'}).change_key_case('CASE_UPPER');

Chaining can be done so long as the function will logically return an
array (I think we should strongly consider allowing functions like
sort() to deviate from PHP though by returning a copy or reference to
the array object even though these normally return a boolean (the
regular php.js sort() functions); users can still use the regular
functions if they want to get a boolean returned as in PHP.) It may be
difficult in some cases where the PHP function returns a useful value
while also operating by reference.

We may also wish for great convenience to allow configuration to be set
which causes the array to return a copy, a deep copy, or act by
reference (regardless of what the PHP functions do). Maybe we could do
that by using one or two '$' to indicate a copy or deep copy (e.g.,
$keys() to return a single-level copy, $$keys() to return a deep copy,
and key() to change the entire original object by reference.

In order to escape from chaining, I've allowed equivalents of array_keys
and array_values (which as with other php.js array functions can also
themselves be used on PHPJS_Array()). While these could conceivably
return a copy and continue chaining (and maybe we should allow an object
to be passed in to indicate this or a special method or something), I
thought it was a basic enough need to want to escape out with the keys
or values as a regular array:

alert(arr.keys()); // returns regular array of the keys (as in array_keys())
alert(arr.keys('search term')); // returns regular array of the keys
with value 'search term' (as in array_keys())
alert(arr.values()); // returns regular array of the values (as in
array_values())
// Use '$' here to mean the methods are not based on PHP--maybe I should
use '_' instead to allow '$' to mean a copy, though that is usually used
for privacy, but then again, this is not that far in meaning from "private"
alert(arr.$object()); // returns non-ordered object representing the
associative array object
alert(arr.$objectChain()); // returns an ordered sequence of objects in
the format used to build the array (an array of key-value pair objects)

3) Working with normal php.js functions

Note in the first example of #2 that I've removed the redundant "array_"
prefix for the object methods. However, we can still make the regular
individual fully-named php.js functions work with these objects too:

ini_set('phpjs.return_phpjs_arrays', 'on');
var arr = array_change_key_case(array({k1: 'a'}, {k2:'b'}),
'CASE_UPPER'); // Uses duck typing internally to determine this is a
PHPJS_Array and thus in this case returns the same but modified
PHPJS_Array object

4) Allowing normal for-in iteration

The PHPJS_Array class goes to some lengths to ensure that the only
properties added to the array are public properties specified by the
user, so that they can filter out prototype properties (or just functions):

for (var p in arr) {
if (arr.hasOwnProperty(p)) {
alert(p+'::'+arr[p]+'\n')
}
}

for (var p in arr) {
if (typeof arr !== 'function') {
alert(p+'::'+arr[p]+'\n')
}
}

So, this allows the reading or (implementation-dependent order)
iteration (but not direct writing, without use of setter methods) of all
properties on the object. Perhaps we should allow a convenient setter
like the following to overwrite existing values without use of splice or
the like:

arr.$('key', value);

(Yes, I love that JS allowed the dollar sign!)

5) Experimental array methods

Since this is our own custom object, I've added the "experimental"
"foreach" method (and may do the same for list()), which acts slightly
differently from PHP (by necessity), though it will be mostly familiar:

arr.foreach(function (key, value) { // Two parameters listed, so passed
key and value
alert(key+'::'+value+'\n')
});

// As with the PHP "foreach $arr as $key=>$value" vs. "foreach $arr as
$value",
// we use the omission of an extra argument to mean that only the
// value will be shown; we do this internally by detecting the
// arity (number of arguments) of the user-supplied function

arr.foreach(function(value) { // Only one parameter listed, so will be
passed the value
alert(value+'\n')
});

And the regular (but experimental) standalone PHP "foreach" function can
also now be used with such PHPJS_Array objects too:

foreach(arr, function (key, value) {
alert(key+'::'+value+'\n')
});

And for those who prefer the naming and value-key ordering of the new
built-in JavaScript methods, we allow a mostly similar API (with the
third argument passed to the callback being the PHPJS_Array, unlike in
JavaScript where it is a genuine (but purely numeric) array):

arr.forEach(function (value, key, obj) {
alert(key+'::'+value+'\n')
});

The big advantage of all of the above foreach versions, besides being
more functional/elegant, is that they, by using genuine arrays
internally, actually iterate in order, unlike even the built-in JS
forEach method.

Feedback or suggestions welcome!

Thanks,
Brett

Reply all
Reply to author
Forward
0 new messages