1

Within PHP, I'd like to be able to iterate over a collection of classes to help with settings, inserting, and validating values. Using a class as a type in method args would make the code more strict which would help avoiding bugs.

I am able to access the collection but only through a public array or method ($values->array or $values->get()). I would like to be able to use $values directly for cleaner code. For example, to access a reference, I'd need to use $values->array[0] or $values->get()[0] instead of $values[0]. How can this be achieved with PHP?

Expected usage:

$values = new Values(
    new Value('foo', 'bar'),
    new Value('foo2', 'bar2'),
);

function handleValues(Values $exampleValues): void
{
    foreach ($exampleValues as $exampleValue) {
        //do something with $exampleValue->field, $exampleValue->value
    }
}

handleValues($values);

Classes:

class Values
{
    public array $array;

    public function __construct(Value... $value){
        $this->array = $value;
    }
}

class Value
{
    public string $field;
    public mixed $value;

    public function __construct(string $field, mixed $value)
    {
        $this->field = $field;
        $this->value = $value;
    }
}
10
  • I think I understand the question, and I have a couple of ideas, but I'm not sure of the best way to answer because I'm having trouble seeing what it's for exactly. Can you add a small example of how a theoretical solution would be used? Commented Feb 22, 2022 at 16:55
  • @MCSharp: If I understand correctly you would like to have a typed array. This does not exist in PHP. I would recommend to extend the class Values from ArrayIterator (but I second Don't Panic that an example would be nice to better understand your issue). Commented Feb 22, 2022 at 17:00
  • Added an example. Having access directly to the array would prevent creating additional variables or calling ->array every time I'd need to access the objects. Commented Feb 22, 2022 at 17:11
  • Look at the iterator interface which Values would implement: php.net/manual/en/class.iterator.php (for the iterating example). Commented Feb 22, 2022 at 17:18
  • Yes, I was also thinking of suggesting implementing the Iterator interface, as well as the ArrayAccess interface. You might find it more trouble than it's worth just to avoid using ->array, (it will take quite a bit of extra code to implement these interfaces) but that would let you effectively use your Values objects like arrays. Commented Feb 22, 2022 at 17:21

1 Answer 1

0

It sounds like what you really want is a typed array, but there is no such thing in PHP.

There is support for documenting typed arrays in a lot of static analysis tools and IDEs, using "PHPDoc syntax" like this:

/** @param Value[] $values */
function foo(array $values) {}

If you want an object that can be looped with foreach, the simplest way is to implement the IteratorAggregate interface, and use it to wrap the internal array in an ArrayIterator object:

class Values implements IteratorAggregate
{
    private array $array;

    public function __construct(Value... $value){
        $this->array = $value;
    }
    
    public function getIterator(): Iterator {
        return new ArrayIterator($this->array);
    }
}

$values = new Values(
    new Value('foo', 'bar'),
    new Value('foo2', 'bar2'),
);

foreach ( $values as $value ) {
    var_dump($value);
}

If you want an object that can be referenced into with [...] syntax, implement the ArrayAccess interface. There are four methods, but each is trivial to implement for this case, and there's an example in the manual.


There's also a built-in ArrayObject class that implements both these interfaces (and a few more), which you can extend to get a lot of array-like behaviour in one go.


On the other hand, if all you want is to validate that the array contains only a specific type, then just do that. A one-line version would be:

$valid = array_reduce($values, fn($valid, $next) => $valid && $next instanceof Value, true);

Or a slightly more efficient version for large arrays (because it stops looping completely when it finds an invalid item):

$valid = true;
foreach ( $values as $next ) {
    if ( ! $next instanceof Value ) {
         $valid = false;
         break;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is the functionality I am looking for. Thanks for providing the examples. One note, getIterator(): Iterator should be getIterator(): ArrayIterator
@MCSharp That's up to you - : Iterator promises that this class, and any child classes, return some descendant of Iterator, which is true. It could also say Traversable (the return value required by the interface), or ArrayIterator, since those are both also true.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.