Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Azure, PHP, OpenXML, VSTS, ...

About the author

Maarten Balliauw is an MVP ASP.NET and is currently employed as .NET Software Engineer at RealDolmen. His interests are mainly web applications developed in ASP.NET (C#) or PHP.
More about me More about me
Send mail E-mail me


Microsoft Most Valuable Professional - MVP - ASP.NET

Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn RealDolmen - Rock-solid passion for ICT
I'm a speaker at TechDays Belgium and TechDays Finland

Search

Latest Twitter

    Follow me on Twitter...

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    © Copyright Maarten Balliauw 2010

    Generic arrays in PHP

    Assuming everyone knows what generics are, let's get down to business right away. PHP does not support generics or something similar, though it could be very useful in PHP development.  Luckily, using some standard OO-practises, a semi-generic array can easily be created, even in multiple ways! Here's the road to PHP generics. 

    The hard way...

    One of the roads to PHP generics is some simple inheritance and type hinting. Let's have a look at PHP's ArrayObject. This class has 2 interesting methods, namely offsetSet() and append(). This would mean I can simply create a new class which inherits from ArrayObject, and uses type hinting to restrict some additions:

    // Example class
    class Example {
      public $SomeProperty;
    }

    // Example class generic ArrayObject
    class ExampleArrayObject extends ArrayObject {
      public function append(Example $value) {
        parent::append($value);
      }

      public function offsetSet($index, Example $value) {
        parent::offsetSet($index, $value);
      }
    }


    // Example additions
    $myArray = new ExampleArrayObject();
    $myArray->append( new Example() ); // Works fine
    $myArray->append( "Some data..." ); // Will throw an Exception!

    The flexible way

    There are some disadvantages to the above solution. For a start, you can't create a generic "string" array unless you encapsulate strings in a specific object type. Same goes for other primitive types. Let's counter this problem! Here's the same code as above using a "GenericArrayObject":

    // Example class
    class Example {
      public $SomeProperty;
    }

    // Validation function
    function is_class_example($value) {
      return $value instanceof Example;
    }

    /**
     * Class GenericArrayObject
     *
     * Contains overrides for ArrayObject methods providing generics-like functionality.
     *
     * @author    Maarten Balliauw
     */
    class GenericArrayObject extends ArrayObject {
        /**
         * Validation function
         *
         * @var     string
         * @access    private
         */
        private $_validationFunction = '';
           
        /**
         * Set validation function
         *
         * @param     string    $functionName    Validation function
         * @throws     Exception
         * @access    public
         */
        public function setValidationFunction($functionName = 'is_string') {
            if ($this->_validationFunction == '') {
                $this->_validationFunction = $functionName;
                return;
            }
           
            $iterator = $this->getIterator();
            while ($iterator->valid()) {
                if (!call_user_func_array($functionName, array($iterator->current()))) {
                    throw new Exception("Switching from " . $this->_validationFunction . " to " . $functionName . " is not possible for all elements.");
                }
               
                $iterator->next();
            }
           
            $this->_validationFunction = $functionName;
        }
       
        /**
         * Append
         *
         * @param     mixed    $value
         * @throws     Exception
         * @access    public
         */
        public function append($value) {
            if ($this->_validationFunction == '') {
                throw new Exception("No validation function has been set.");
            }
           
            if (call_user_func_array($this->_validationFunction, array($value))) {
                parent::append($value);
            } else {
                throw new Exception("Appended type does not meet constraint " . $this->_validationFunction);
            }
        }
       
        /**
         * offsetSet
         *
         * @param     mixed    $index
         * @param     string    $newval
         * @throws     Exception
         * @access    public
         */
        public function offsetSet($index, $newval) {
            if ($this->_validationFunction == '') {
                throw new Exception("No validation function has been set.");
            }
           
            if (call_user_func_array($this->_validationFunction, array($newval))) {
                parent::offsetSet($index, $newval);
            } else {
                throw new Exception("Appended type does not meet constraint " . $this->_validationFunction);
            }
        }
    }

    // Example additions
    $myArray = new GenericArrayObject();
    $myArray->setValidationFunction('is_class_example');
    $myArray->append( new Example() ); // Works fine
    $myArray->append( "Some data..." ); // Will throw an Exception!

    Using this flexible class, you can simply set a validation function on the GenericArrayObject, which enabels you to use PHP's built-in functions like is_string (string-only ArrayObject), is_int, ... You can even write a small validation function which matches a string against a regular expression and for example create an e-mail address ArrayObject rejecting any string that does not match this regular expression.


    Comments

    blog.diegotremper.com |

    Monday, October 22, 2007 9:43 PM

    pingback

    Pingback from blog.diegotremper.com

    Tremper’s Blog » Genéricos em PHP

    TiTerm France |

    Tuesday, October 23, 2007 10:41 AM

    TiTerm

    Very interesting approach.
    Could be usefull.

    I'd like to know what tool you had used to make those beautiful UML diagrams.

    Thanks

    Maarten Belgium |

    Tuesday, October 23, 2007 10:48 AM

    Maarten

    That would be Microsoft Visual Studio 2005 Smile

    Stoyan Bulgaria |

    Tuesday, October 23, 2007 11:20 AM

    Stoyan

    Hmm it's seems to be a great technique, though I can't figure ou it's usage in my current projects at the moment.
    I think it would be more readable (in the sense of OOP manner) to define validator class, not a function:

    abstract class Validate {

      abstract function isValid($value) {}
    }

    class MyValidate extends Validate {

      public function isValid($value) {
        return $value instanceof Example;
      }
    }

    then something like :

    $myArray->setValidationClass('MyValidate');

    The GenericArrayObject could then check if MyValidate is a valid Validate class and throw the corresponding Exception if not. I.e. GenericArrayObject could act as a Visitor.

    blog.sigmalab.net |

    Thursday, October 25, 2007 1:04 AM

    pingback

    Pingback from blog.sigmalab.net

    SigmaLab  » Blog Archive   » Link della Settimana

    developercast.com |

    Thursday, October 25, 2007 4:44 PM

    pingback

    Pingback from developercast.com

    developercast.com » Maarten Balliauw’s Blog: Generic arrays in PHP

    mt-soft.com.ar |

    Friday, October 26, 2007 4:11 AM

    pingback

    Pingback from mt-soft.com.ar

    Generic arrays in PHP | MT-Soft Website Development

    Jesdisciple (Chris) United States |

    Tuesday, November 06, 2007 12:27 AM

    Jesdisciple (Chris)

    I agree, Stoyan. In fact, my non-OOP implementation (double implementation, actually - a Map class) currently uses two strings $keyValidation and $valueValidation, which get sent to eval() in validateKey() and validateValue(). My next task is to convert this to an interface (and consolidate my two validation functions into one).

    P.S. I like your live preview; that's not something I had thought of using JavaScript for.

    kyle Germany |

    Thursday, November 08, 2007 11:42 AM

    kyle

    Even Stoyan uses a dynamic solution for a static problem: If one changes the validation strategy during runtime, your array becomes invalid. So I recommend to place (and use!) the strategy in the generic base class and force the user of GenricObjectArray to implement the strategy.

    <?php
    abstract class GenericArrayObject
    {
    ...
    public abstract static function isValid( $value );
    ...
    }
    ?>

    In append() and offsetSet() check, if $this->isValid( $value ) ist true and proceed. Otherwise throw an InvalidArgumentException. (Using $this avoids late binding problems - otherwise use get_class($this) and call_user_func())

    Subclasses just have to implement isValid() (e.g. $value instanceof X ) - wich even might check value ranges or whatever.

    maartenba Belgium |

    Thursday, November 08, 2007 11:48 AM

    maartenba

    Kyle, that is true, except for primitive types like string etc. For real objects, the isValid() method is indeed more appropriate.

    kyle Germany |

    Thursday, November 08, 2007 12:16 PM

    kyle

    Even for primitive types the solution works well:

    class StringObjectArray extends GenericArrayObject
    {
      public static function isValid( $value )
      {
        return is_string($value);
      }
    }

    As the user implements the strategy in his subclass he even may guarantee that only integers > 10 are stored:

    class IntegerGt10ObjectArray extends GenericArrayObject
    {
      public static function isValid( $value )
      {
        return is_integer($value) && $value > 10;
      }
    }

    PS: Remember to check the initializing $array of ArrayObjects constructor - if any of the values is not valid -> InvalidArgumentException.

    ninjapenguin.co.uk |

    Sunday, November 11, 2007 5:50 PM

    pingback

    Pingback from ninjapenguin.co.uk

    Ninjapenguin - Home page of Matthew Wells  » Blog Archive   » Weekly Update

    hangy Germany |

    Sunday, November 23, 2008 1:06 PM

    hangy

    Maarten, Interesting approach! Generic lists and list interfaces which could be used to assure type safety for an API are something badly missing in PHP at the moment. Using PHP's dynamic (and powerful) arrays can be really nice, but if you need some type safety because you implement a plugin system or something like that, I find it hard to trust people not to screw up by providing invalid data.

    I'd like to know what tool you had used to make those beautiful UML diagrams.
    The diagrams which Visual Studio creates are quite cool and also easy to understand, but, technically speaking, they are no UML class diagrams.

    If one changes the validation strategy during runtime, your array becomes invalid. So I recommend to place (and use!) the strategy in the generic base class and force the user of GenricObjectArray to implement the strategy.
    You are absolutely right by saying that setting the array to an invalid state after elements have successfully been added is a bad idea. Also, your idea of forcing a this to be subclassed to provide an isValid strategy is good, but that would mean that the class is not generic any more.
    In order to achieve a generic type safe array and avoiding the issue you pointed out, one could simply remove the setValidationFunction and just allow setting that function in the GenericArrayObject's constructor. That way, you would still have a generic (ie. no need to subclass it for a different type, just use a lambda function to validate the content) array and assure that the content is valid at all time. Smile

    answerspluto.com |

    Monday, July 13, 2009 9:34 PM

    pingback

    Pingback from answerspluto.com

    list of urls 4 « Answers Pluto

    jaceju.net |

    Wednesday, November 25, 2009 3:49 AM

    pingback

    Pingback from jaceju.net

    網站製作學習誌 » [Web] 連結分享

    topsy.com |

    Thursday, November 26, 2009 12:32 AM

    pingback

    Pingback from topsy.com

    Twitter Trackbacks for
            
            Generic arrays in PHP
            [maartenballiauw.be]
            on Topsy.com

    Comments are closed