LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Thursday, January 18. 2007

Overloading arrays in PHP 5.2.0

Update: I ran into issues with the ArrayObject solution, as there was a bug in PHP 5.2.0 (now fixed) with its interaction with empty() and isset() when used with the ARRAY_AS_PROPS flag. I tried a number of fixes, but eventually my friend Mike pointed out something I'd missed: as of PHP 5.1, setting undefined public properties no longer raises an E_STRICT notice. Knowing this, you can now do the following without raising any errors:


class Foo
{
    public function __set($key, $value)
    {
        $this->$key = $value;
    }
}

$foo        = new Foo();
$foo->bar   = array();
$foo->bar[] = 42;
 

This is a much simpler solution, performs better, and solves all the issues I was presented. Thanks, Mike!


Several weeks back, a bug was reported against Zend_View that had me initially stumped. Basically, the following was now failing in PHP 5.2.0:


$view->foo   = array();
$view->foo[] = 42;
 

A notice was raised stating, "Notice: Indirect modification of overloaded property Zend_View::$foo has no effect."

I'd read about this some months back on the php internals list, but at the time hadn't understood the consequences. Basically, __get() no longer returns a reference and returns values in read mode, which makes modifying arrays using overloading impossible using traditional methods.

Derick Rethans blogged about the issue in August. His solution was to use a switch() statement in __get() to cast the returned value explicitly as an array:


public function __get($key)
{
    if (is_array($this->_vars[$key])) {
        return (array) $this->_vars[$key];
    }

    return $this->_vars[$key];
}
 

The problem with this approach is that you then have issues with other array functionalities, such as assigning by reference.

After some work, I found the best solution was to have the class extend ArrayObject, but with a slight twist:


class My_Class extends ArrayObject
{
    public function __construct($config = array())
    {
        // ... some setup

        // Allow accessing properties as either array keys or object properties:
        parent::__construct(array(), ArrayObject::ARRAY_AS_PROPS);
    }
}
 

This combination allows some very flexible access to properties in the object:


// from the original example:
$view->foo   = array();
$view->foo[] = 42;

echo $view['foo'][0]; // '42'
echo $view->foo[0];   // same
 

One issue that was always difficult to work with in Zend_View was keeping 'public' properties -- template variables -- separate from private/protected properties (things like the helper, filter, and script paths). Since those properties are pre-declared in the class, the ArrayObject::ARRAY_AS_PROPS setting prevented any such collision from happening -- and helped simplify the code.

Moral of the story? If you need to be able to modify overloaded arrays in your class, and support PHP 5.2.0, extend ArrayObject.

Posted by Matthew Weier O'Phinney in PHP at 15:39 | Comments (12) | Trackback (1)

Trackbacks
Trackback specific URI for this entry

[ZF-1743] Zend_Session_Namespace does not persist new slice data
Note: other projects have also come across this problem and have fixed and suggested to move up to 5.2.1. 5.2.0 is simply broken when it comes to __get() behavior, and forcing a referenced return does not work for that version.. This should be taken...
Weblog: JIRA: Zend Framework
Tracked: Aug 02, 17:56

Comments
Display comments as (Linear | Threaded)

You can not assign things returned by __get by reference anyway, so using the (array) cast there is perfectly fine... Unfortunately this breaks in php-5.2.1dev again, but I am on that :-)

ArrayObject has some other problems (such as you'd have to change your "array" type hints to something else), but it does do the job in most places.
#1 Derick (Link) on 2006-12-28 14:41 (Reply)
Actually, I just said that you can do it. I don't believe there's anything special since 5.1. As far as I know, this has never raised E_STRICT in any PHP 5.x version:

$foo = new stdclass(); $foo->bar = 'baz';

What does raise an E_STRICT in all PHP 5.x versions is trying to set a property on an undefined object (or creating a "default object"):

$foo->bar = 'baz'; // never defined $foo
#2 Mike (Link) on 2007-01-18 20:22 (Reply)
Hi Matthew,

thanks for this great tip.
It was very nice to read!

Best Greetings from Germany
Jens
#3 Jens on 2007-01-19 20:51 (Reply)
I need to try this tip ;-) Thanks.
#4 sf (Link) on 2007-01-21 16:00 (Reply)
Thanks, for the usefull PHP tip
http://ptalus.blogspot.com
#5 ptalus (Link) on 2007-10-10 06:23 (Reply)
don't blame me a fool, but what about:

class Container {}

$test = new Container();
$test->nevertheless['sowhat'] = "anything";

for sure this is completly insane, but works. As this is fact, I'm wondering much more about the show the php crew makes about the overloading...
#6 Julian on 2008-01-24 18:44 (Reply)
That's the exact approach I took in Zend_View, actually. The problem is that it's harder to aggregate all the variables (get_object_vars() returns private and protected variables, based on the version of PHP you're running), which was the main purpose in using overloading in the first place.

Overloading is useful for when you want to arbitrarily allow setters that contain logic -- such as normalizing a value before storing it. That way you can have the accessors, but also simply treat it as a property, and still obtain the same results.
#6.1 Matthew Weier O'Phinney (Link) on 2008-01-24 19:46 (Reply)
interesting, but how do you realize a setter for your data. Any Attempts to realize a "magical" version didn't work, so your setter can only be "real" setters for defined and expected values. plese correct me, if I'm wrong...
#6.1.1 Julian on 2008-01-25 10:23 (Reply)
Setters are not the issue; getters are. __get(), in 5.2.0, did not return by reference, so any attempt to set values in arrays accessed by __get() failed. __set() worked as expected -- except with arrays (due to the reference issue).

So, Zend_View uses __set() to ensure that view variables do not contain a leading underscore (indicating protected or private status), and then creates the variable as a public property (these do not need to be declared; PHP is lenient in this regard). __get() intercepts variables that do not exist, and returns null if they do not (thus avoiding raising a notice for accessing non-existent properties).

Another use for __set() and __get() is for proxying to other objects, as well as to restrict what types of variables may be set as object properties. I'm using this in Zend_Form to limit properties to elements, display groups, and sub forms. Fortunately, PHP has no problem with using object properties.
#6.1.1.1 Matthew Weier O'Phinney (Link) on 2008-01-25 10:31 (Reply)
I know about the reference issue. It made me a lot of problems and leaded me to you blog here finally.

I do not understand, where you place the __set and __get. Certainly not in the extended class (ArrayObject), where they are not called. If you put them in a surrounding class with the extended class as a protected Container you face the reference issue again. I'm a little bit confused
#6.1.1.1.1 Julian on 2008-01-25 10:47 (Reply)
The final solution does not use ArrayObject or any other container (this post is a little outdated); it simply uses public object properties, and a little magic with __get() and __set() to allow for some custom behaviour (return null for undefined properties, don't allow properties beginning with underscores, etc.).

Take a look at Zend_View_Abstract, which you can find at:

http://framework.zend.com/svn/framework/trunk/library/Zend/View/Abstract.php

to see what I did there; in particular, look at the __get() and __set() methods.

When the property is a public property, and not a virtual property (as in when __get() proxies to a protected class member), the reference issue does not exist -- there are no references involved.
#6.1.1.1.1.1 Matthew Weier O'Phinney (Link) on 2008-01-25 10:55 (Reply)
i see, i solved the problem in a similar way, just with __get for some . I use it for multiple containers within a request helper and Environment Singleton. I think this is the only way to realize something like this actually without some really sick constructions, but I still hope, that the php crew especially dimitry is changing something at the __get behaviour. let's see...
#6.1.1.1.1.1.1 Julian on 2008-01-28 15:02 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back November '08 Forward
Mon Tue Wed Thu Fri Sat Sun
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

November 2008
October 2008
September 2008
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml dojo
xml dpc08
xml file_fortune
xml linux
xml mvc
xml oop
xml pear
xml personal
xml php
xml phpworks08
xml programming
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net