4

I have a class called Rule and I'm about to create a RuleContainer class that's actually an array of Rule objects.

I wonder if there is an alternative of creating a new class. Is there any (modern) way to approach this problem? That is, something like using SPL to define an array that only allows adding objects of a specific class.

If not, which interface should I implement in my RuleContainer class?

5 Answers 5

2

The most suitable class for your task would be SplObjectStorage, but it doesn't allow for class typehint.

I think, you could do as follow:

class RuleContainer extends SplObjectStorage
{
    function attach(Rule $rule)
    {
         parent::attach($rule);
    }

    function detach(Rule $rule)
    {
         parent::detach($rule);
    }
}

and so on. You can read for SplObjectStorage interface on php.net and decide, what will you use and what needs overriding.

Sign up to request clarification or add additional context in comments.

1 Comment

After some experimentation I discovered that the attach() and detach() signatures can NOT be changed, so it should be attach($rule, $inf = null) ... but of course you can do the type checking inside the method.
1

In your case, I would implement the Iterator interface in the RuleContainer, as I've done several times when I needed a sort of Collection<T> as we know it from other (typed) languages. And in the add(Rule $item) or addItem(Rule $item) method I'd make sure with the type definition of the argument (or using instanceof) that the item to be added is of type Rule.

Comments

1

Depending on the usage patterns for your container class, you need to implement one or more of these interfaces:

  • Iterator - to use it as foreach($container as $key => $value);
  • Countable - for count($container);
  • ArrayAccess - for $container[$key] (set it, get it, check if it isset(), unset() it);

Comments

1

Usage of PHP array-routines interfaces

You may achieve your goal with, for example, ArrayAccess implementation. Together with Iterator it will look like:

class ArrayStorage implements Iterator, ArrayAccess
{
    private $holder = [];
    
    private $instanceName;
    
    public function __construct($instanceName)
    {
        if (!class_exists($instanceName)) {
            throw new \Exception('Class '.$instanceName.' was not found');
        }
        $this->instanceName = $instanceName;
    }
    public function rewind() 
    {
        reset($this->holder);
    }
    
    public function current() 
    {
        return current($this->holder);
    }

    public function key() 
    {
        return key($this->holder);
    }

    public function next() 
    {
        next($this->holder);
    }

    public function valid() 
    {
        return false !== $this->current();
    }
    
    public function offsetSet($offset, $value) 
    {
        if (!($value instanceof $this->instanceName)) {
            throw new \Exception('Storage allows only '.$this->instanceName.' instances');
        }
        if (is_null($offset)) {
            $this->holder[] = $value;
        } else {
            $this->holder[$offset] = $value;
        }
    }

    public function offsetExists($offset) 
    {
        return isset($this->holder[$offset]);
    }

    public function offsetUnset($offset) 
    {
        unset($this->holder[$offset]);
    }

    public function offsetGet($offset) 
    {
        return isset($this->holder[$offset]) ? $this->holder[$offset] : null;
    }
}

Procs

So - yes, you are doing instanceof check explicitly, but end user of your class doesn't know about that. It will only be possible to operate on valid instances in context of this storage (you can check this fiddle for usage sample). Concept is like:

$storage = new ArrayStorage('Foo'); //define what we will accept
$storage[] = new Foo; //fine, [] array-writing
$storage['baz'] = new Foo; //fine, key set
foreach ($storage as $key => $value) {
    echo($key. ' => '.PHP_EOL.var_export($value, 1).PHP_EOL);
}
//invalid, will not pass. Either throw exception or just ignore:
$storage['bee'] = new Bar; 

End fail-check behavior is up to you, but, my opinion, throwing exception is the best choice here as they are catchable, thus, end user may decide what to do in this case. Further option may be to add Countable to the storage, but it won't change generic idea.

And cons

Downside - no, you will not be able to "typehint" it somehow. While it is useful, in doc blocks you still will need to show what kind of entity are you accepting. In terms of general language features, there is arrayof RFC, by Joe Watkins, which was proposed for PHP version 5.6, but, unfortunately, failed. May be it will be reconsidered in next versions releases.

2 Comments

Aye, you can "typehint" (with all due respect) ;-) Using the class as a type of the argument $value in the offsetSet() method. Unfortunately this only works with objects and arrays, but still... codepad.viper-7.com/ESEr8B
In offsetSet() - yes, you can, but this is pointless to end users (they don't even know about offsetSet(), they are just using the storage as it is). I was telling about - that if some method expects array of exactly some instances - it's impossible to typehint that with this implementation (in typehint will be ArrayStorage $x - and no information about what is inside)
0

You can make RuleContainer yourself (as you say) and do all sorts of cleverness to manually enforce it but you live in the real world I live in the real world and you simply don't need a container object for this, just use an array.

If your problem is simply one of enforcement of the subject object type a lá List<className>() you can't do this in PHP and to be honest it's of debatable use in the languages where it is found (I know I will get down voted for saying this, but I will still be right) //excepting it helps further clarify the purpose of the list// In all honesty my 20+ years of programming across almost all the languages there is (except machine code perl and fortran), I can tell you such constructs and simply not worth the human overhead and themselves can include indirect unintended burdens way over their actual worth:

An easy compromise is: no laziness, start naming the array more than something like tmpList and if you are absolultey determined implment a simple test to http://php.net/manual/en/function.get-class.php at the start of the forloops you surely eventually use

3 Comments

The thing is that firstly I thought I couldn't do in PHP. I have been working with PHP more than 10 years, and used pure arrays all the time. But recently I wondered if there is a better way using any >5.3 features. It looks like you can use SplObjetcStorage for this (as it says the accepted answer). Thank you anyway.
yes, this is still the container object principle though, (you could roll your own with very little hassle) I still think writing your own .Add() with get_class() -> Exception could be done as easily as typing extend on a object no PHP developer will have heard of.. but there u go, goes back to my point of unintended consequences..
NB: PHP is JIT - you could use the very valid solution of just throw an exception in a forloop too. remember that PHP is never a "compiled" language it is a JIT compiled language. i.e. there is no middle man. If an error threw in a loop it would still happen at run time just as it would at compile in (say) C#.. since this distinction is absent you (and other developers) would benefit equally from a exception in a for loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.