Support Traversable interfaces in mock objects #604

Closed
webmozart opened this Issue Jul 13, 2012 · 10 comments

Projects

None yet

4 participants

@webmozart

Currently, PHPUnit fails when trying to mock an interface that implements \Traversable because PHP expects implementations of that interface to also either implement \Iterator or \IteratorAggregate.

<?php

interface MyInterface extends \Traversable {}

// doesn't work
$this->getMock('MyInterface');

The fix currently is to extend the interface for testing purposes and to add one of the two required interfaces (Note: The order of the interfaces is important here!)

<?php

interface TestMyInterface extends \Iterator, MyInterface {}

// works
$this->getMock('TestMyInterface');

IMO PHPUnit should be able to add that interface automatically in such situations, i.e.:

  • when the mock is created for an interface or an abstract class, and
  • that interface/abstract class implements \Traversable, but not \Iterator nor \IteratorAggregate
@edorian
Collaborator
edorian commented Jul 27, 2012

He,

thanks for the detailed bug report. I'm currently failing to understand which use case requires one to extend the traversable interface.

It should work to throw that into the mock building but I'd like to state a use case in the tests for that.

@webmozart

I'm currently failing to understand which use case requires one to extend the traversable interface.

Any interface whose implementation is supposed to be traversable needs to extend it. A very simple example:

<?php
interface ArrayInterface extends \ArrayAccess, \Countable, \Traversable {}

// The implementation can chose to implement \Iterator...
class Array1 implements \Iterator, ArrayInterface
{
    ...
}

// or \IteratorAggregate
class Array2 implements \IteratorAggregate, ArrayInterface
{
    ...
}

Interfaces usually specify a contract, without specifying how that contract should be implemented. These core interfaces are special in that \Traversable specifies the contract ("This object should be iterable") while \Iterator and \IteratorAggregate specify how that contract is implemented. Consequently, no userland interface should them, but always \Traversable IMHO.

@edorian
Collaborator
edorian commented Jul 27, 2012

So what the interface is saying there is "I can foreach over things that implement this but i don't care how the implementing class makes that happen (Iterator or IteratorAggregate).

Makes sense to me. Thanks for explaining :)

@webmozart

Exactly :)

@sebastianbergmann
Owner

The PHP Manual states "This is an internal engine interface which cannot be implemented in PHP scripts. Either IteratorAggregate or Iterator must be used instead." While the documentation continues with "When implementing an interface which extends Traversable, make sure to list IteratorAggregate or Iterator before its name in the implements clause.", I do not think that Traversable should be used in userland at all and therefore am against adding support for this edge-case to PHPUnit.

@webmozart

@sebastianbergmann IMHO you are misquoting the manual. The manual talks about implementing Traversable, not extending it.

As I have shown above, there is a very clear and valid use case for extending Traversable in other interfaces (without contradicting the manual you quoted).

@beberlei
beberlei commented Oct 7, 2012

If the traversable interface was internal to the engine, it would not be exported to userland.

@sebastianbergmann
Owner

I would accept a pull request for phpunit-mock-objects that implements the fix described in the original ticket.

@webmozart webmozart referenced this issue in sebastianbergmann/phpunit-mock-objects Oct 7, 2012
Closed

Support Traversable interfaces in mock objects #103

@webmozart

Ok. I opened a corresponding ticket in that repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment