Protecting the Iterator s Contents by using the Clone Operator

Protecting the Iterator's Contents by using the Clone Operator

The code as it stands now could be considered complete if all it would ever be used for is the display of the contents of the Collection. But what if you need to alter the Collection's contents on the fly while inside the foreach loop? In the code that follows, you loop through the list of courses for a given student looking for courses that require a special fee (a laboratory supplies fee). If the student is not currently in good financial standing, you need to remove the student from the course and display an error message.

   <?php    function checkCourseFees(Student $objStudent) {      foreach($objStudent->courses as $key => $objCourse) {        if($objCourse->hasSpecialFee && !$objStudent->inGoodStanding() ) {          $objStudent->courses->removeItem($key);          print("Sorry, because of your financial standing we " .                "had to remove you from $objCourse");        }      }    }    ?> 

This code looks perfectly safe and reasonable. On the surface, nothing appears wrong with removing an element of the collection while iterating over its contents. But this code is in fact a big no-no and has the potential to cause strange bugs. You have to look under the hood to see why.

Assume that this Student has three Courses associated with it, two regular and one with a fee.

   $objStudent->courses->addCourse(new Course(123)); //no fee, key is 0    $objStudent->courses->addCourse(new Course(987)); //_has_ fee, key is 1    $objStudent->courses->addCourse(new Course(456)); //no fee, key is 2 

When you pass $objStudent->courses into the foreach statement, the getIterator() method is invoked. The CollectionIterator, in its constructor, creates the internal $_keys array by calling $_collection->getKeys(). When you iterate to the first element, $_currIndex is 0. You fetch the first course. No problems yet. On the second iteration, $_currIndex is 1. This course has a fee, so you remove it from the collection. Now you have a problem.

Because the collection held internally in the Iterator was passed by reference, the call to $objStudent->courses->removeItem() affects the collection held in the Iterator. Now the $_collection member variable has only two items, with keys 0 and 1. The attempt to fetch the third key, value of 2, will fail unexpectedly. To counter this problem, you need to do two things.

First, don't modify the collection while iterating through its members. Additions and deletions should be done after the foreach loop. Second, to reinforce the idea that the Iterator is acting on the Collection as it was at the time of the creation of the Iterator, you need to pass in a copy of the Collection, rather than a reference to it.

The clone operator should be used when passing the argument to the Iterator constructor in the getIterator() method of the Collection class. The clone operator creates a copy of the operand and is the opposite of the & function parameter modifier. Modify Collection::getIterator() as follows:

   public function getIterator() {      $this->_checkCallback();      return new CollectionIterator(clone $this);    } 

This creates a copy of the current object and passes that copy to the Iterator. PHP creates this copy by instantiating a new Collection (without invoking the constructor) and copying all the private and public member variables from the original to this copy. If you have some sort of special activities that need to happen when one of your objects is cloned, you can define a private method called__clone() that will take the place of PHP's default action. Note that the__clone() function is private and an error will be thrown if you try to invoke it directly. Always use the clone operator when you need a copy of an object.

Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
Year: 2003
Pages: 182
BUY ON AMAZON © 2008-2017.
If you may any questions please contact us: