基本的Iterator类和字符串遍历

I know this is a simple one, but I just can't figure out where the order is wrong.

<?php

class StringIterator implements \Iterator
{
private $_string;
private $_length;   
private $_position;


public function __construct($string)
{
    if(empty($string))
    {
        throw new \InvalidArgumentException(sprintf('The specified string is empty'));
    }//end if

        $this->_string = $string;
        $this->_length = strlen($this->_string);
        $this->rewind(); //setting the initial position instead of having it all over the place.

}//end func 

public function current()
{
 return $this->_string[$this->_position];

}//end func

public function key()
{
    return $this->_position;
}//end func



public function rewind()
{
    $this-> _position = -1;
}//end func


public function next()
{
    $this-> _position++;
}//end func


public function valid()
{   //why is it that your doing this instead of...
/*
*   return isset(this->_string[this->_position]);
*
*
*/
    return $this->_position < $this->_length;
}//end func

}//end class

TEST CLASS
<?php

require_once __DIR__. '/../src/Iterators/StringIterator.php';

class StringIteratorTest extends PHPUnit_Framework_TestCase
{
    //each method must begin with TEST
public function testInitializing()
{

    $iterator = new \Iterators\StringIterator("Hello World");

    $this->assertEquals(true,$iterator->valid()); 


}//end func

/**
*   @expectedException InvalidArgumentException
*/
public function testInitException()
{

$iterator = new \Iterators\StringIterator("");

}//end func


public function testTraverse()
{
    $string ="Hello World";
    $iterator = new \Iterators\StringIterator($string);
    $count =0;

    $iterator->rewind();
    //test to make sure the next() runs.


    //The iterator interface defines the method Key key()= $key
    //Iterator::current() = $char (gets the current value at the position)
    foreach($iterator as $key=>$char)
    {
        $this->assertEquals($count,$key);
        $this->assertEquals($string[$count],$char);
        ++$count;
         $this->next();
    }//end 4e

}//end func

//tests that the internal pointer (it) is at a valid position in that container that is being iterated
public function testValid()
{
$iterator = new \Iterators\StringIterator($string);

}//end func

//tests the rewind method back to the start of the container.

public function testRewind()
{
$string="Bye";

$iterator = new \Iterators\StringIterator($string); 


for( $i = 0; $i< strlen($string) + 1; ++$i){

$iterator->next();

}//end for
$this->assertEquals(false,$iterator->valid());
$iterator->rewind();
$this->assertEquals(true,$iterator->valid());

}


}

The problem in question: When I run the test (phpunit test) it is stating there is a error in the current() (return line) and in the foreach loop from the actual test component

foreach($iterator as $key=>$char)
{
    $this->assertEquals($count,$key);
    $this->assertEquals($string[$count],$char);
    ++$count;
    $this->next();
}//end 4e

From my research I know that it has to do with the order I'm calling next in the foreach loop, I just can figure out exactly what it needs to be..

There seems to be a lot wrong here:

  • You're calling $this->next() within StringIteratorTest::testTraverse(). This method has no such next() method. That method belongs to the StringIterator class. This should be a fatal error.
  • Even if this code were running within the StringIterator class, you still wouldn't need to call next() from within a foreach loop. foreach calls all of the Iterator-defined methods itself. That's the whole point. Calling next() within a foreach would just have the effect of jumping a position.
  • Your rewind() method is incorrect. It's setting the position to a negative value. There are no negative positions in a string. If you were to call current() on this, you would get an error because it's trying to call $_string[-1], which doesn't exist.
  • Your valid() method is also incorrect, since it's only checking that the position isn't outside the upper limit, but not the lower limit. This is why valid() is returning TRUE even though your rewind() method is setting the position to an invalid state.
  • Your testValid() method should have caught this, but that function isn't actually testing the valid() method. It's just creating a new object and doing nothing with it.
  • Your testing methodology is bad in the testRewind() method. Rather than checking for valid(), you should be calling current() and checking if it returns "B", which is the first character in your string. The main function of the rewind() method is to reset the internal pointer of the object back to the start, so you should be testing for that explicitly.