Trying to enforce Array type safety, does $array[] resolve to $array->offsetSet()?

Well in PHP’s ArrayObject and SplFixedArray you get this method called offsetSet(). I personally dont think its an elegant way of setting array element, and Id prefer $a[$i] = $val rather than $a->offsetSet($i, $val). So I wonder, is the array syntax $a is equivalent to calling offsetSet implicitly? If true, it will be possible to enforce type safety by subclassing Array class and overriding offsetSet method, as shown below:


class StringArray extends SplFixedArray{
   public function offsetSet($index, $value){
       if(!is_string($str)) throw new IllegalArgumentException("Cannot insert a nonstring element to string array");
       parent::($index, $value);
   }
}

$stringArray = new StringArray(5);
$stringArray->offsetSet(0, "Hello");
$stringArray->offsetSet(1, "World");
$stringArray->offsetSet(2, 10);  // Error, an exception will be thrown since 10 is not a string object.

But is the array syntax gonna work? Anyone ever tried out this idea?


$stringArray = new StringArray;
$stringArray[0] = "Hello";
$stringArray[1] = "World";
$stringArray[2] = 10; // Will the script throw an exception, or allow it to pass since its not implicitly calling offsetSet()?

Have you tried it? I don’t understand. Looks like you’ve written up a basic test case.

Well the second set doesnt define a length of the StringArray object, so it’ll fail when you try and insert anything (because the size defaults to 0). But there should be no error generated by the adding of an integer value in that method, because offsetSet is not called.

Yes, if you are doing an assignment operation. If you are getting the value offsetGet is called.

If true, it will be possible to enforce type safety by subclassing Array class and overriding offsetSet method

Which is the whole point of the ArrayObject class. I use it a lot.

Keep in mind exchangeArray could be used to make an end run around your type restrictions, so you’ll need to override it as well. Also, the storage of an ArrayObject is private, so the only way your class can act upon the data internally is through this array access ($this[$i] works with an ArrayObject). So be certain that whatever restrictions you place are internally harmonious.

Yes, using the array-style syntax will call the offset* methods. That is the entire purpose of the ArrayAccess interface that SplFixedArray and ArrayObject both implement.

You can do whatever you like inside your overriding methods, including throwing exceptions.

I see, thats excellent. So I can use this notation in the overriden method offsetSet($index, $value) too?

class StringArray extends SplFixedArray{
   public function offsetSet($index, $value){
       if(!is_string($str)) throw new IllegalArgumentException("Cannot insert a nonstring element to string array");
       $this[$index] = $value;
   }
}

I wonder what is the difference when it comes to performance factors. Is it faster or slower than calling parent class’ offsetSet() method in child method?

You’d have to setup a benchmark for that, but I imagine you’d only be able to notice the difference when working with nearly a million records. Until that point, it will probably be negligible (at least that’s true for C#).

I see, sounds great, I know PHP is about 10-20x slower than C# but still its unlikely to reach near 100k records anyway.

On a side note, I wonder if this way of typehinting works. Lets say I have both StringArray and NumberArray extending from the parent SplFixedArray class(apparently they are designed to be typesafe), will the below code be valid? Or am I not allowed to change the typehinting mechanism and instead have to validate subtypes inside the method body?


interface ArrayPringer{
    public function print(SplFixedArray $array);
}

class StringPrinter implements ArrayPrinter{
    public function print(StringArray $strings){
        // print strings
    }
}

class NumberPrinter implements ArrayPrinter{
    public function print(NumberArray $numbers){
        // print numbers
    }
}

I usually just let people be, but come on! Are you afraid to run code or something? :slight_smile: Go try it and find out. (with and without strict error reporting)

Nope, its just that I use a server from a webhost and its quite annoying to have to upload/modify code using FTP. The FTP client cuteftp always disconnects without a reason, its good when you are uploading files but editing files in ftp client is a pain…

Anyway I find out PHP does not allow derived classes to narrow the scope of typehinting. There was a PHP bug ticket years ago, and unfortunately it has yet to be fixed, or most likely never will be:
https://bugs.php.net/bug.php?id=37854

Funny changing typehinting is actually working if the method is not abstract(defined abstract in abstract class or interface), even if you change the typehinting to a completely irrelevant class rather than a subclass. PHP is indeed full of surprises.

Danger Will Robinson! Danger!!

The code above creates infinite recursion. Using $this[$key] = $val invokes the class’ offsetSet method. So you are invoking it within itself, which is a loop that never resolves. Within the overriding offsetSet function you must call the parent explicitly with parent::offsetSet( $key, $val);

Yes it did create an infinite loop, thanks for mentioning. Just fixed it now, looks like I tend to run into this kind of problems. A few weeks ago I had a problem with cloning objects, which also results in infinite loops as in my __clone() method I end up returning $this. XD