Collections: Iterating

Collections are iterated via foreach.

<?hh

namespace Hack\UserDocumentation\Collections\Iterating\Examples\FEach;

function run(): void {
  $vec = Vector {'A', 'B', 'C'};
  $map = Map {'A' => 1, 'B' => 2};
  $set = Set {800, 900, 1000};
  $pair = Pair {'A', 'B'};

  foreach ($vec as $val) {
    var_dump($val);
  }

  foreach ($map as $key => $val) {
    var_dump($key);
    var_dump($val);
  }

  foreach ($set as $val) {
    var_dump($val);
  }

  foreach ($pair as $key => $val) {
    var_dump($key);
    var_dump($val);
  }
}

run();
Output
string(1) "A"
string(1) "B"
string(1) "C"
string(1) "A"
int(1)
string(1) "B"
int(2)
int(800)
int(900)
int(1000)
int(0)
string(1) "A"
int(1)
string(1) "B"

Caveats

There are a couple of caveats to be aware of when using foreach to iterate over a collection.

Modifying the Collection

Adding or deleting an item from the collection is not allowed and, if tried, will result in an InvalidOperationException.

<?hh

namespace Hack\UserDocumentation\Collections\Iterating\Examples\Modifying;

function run(): void {
  $vec = Vector {'A', 'B', 'C'};

  try {
    foreach ($vec as $val) {
      if ($val === 'B') {
        // This will actually be allowed, but the next time through the loop
        // the InvalidOperationException will be thrown.
        $vec[] = 'D';
      }
      var_dump($vec);
      var_dump($val);
    }
  } catch (\InvalidOperationException $ex) {
    var_dump($ex->getMessage());
  }

  $set = Set {100, 200, 300};

  try {
    foreach ($set as $val) {
      if ($val === 300) {
        // This will actually be allowed, and even though this is the last
        // element in the set, we actually do the check in the foreach again so
        // the InvalidOperationException will be thrown.
        $set->remove(300);
      }
      var_dump($set);
      var_dump($val);
    }
  } catch (\InvalidOperationException $ex) {
    var_dump($ex->getMessage());
  }
}

run();
Output
object(HH\Vector)#1 (3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
string(1) "A"
object(HH\Vector)#1 (4) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
  [3]=>
  string(1) "D"
}
string(1) "B"
string(40) "Collection was modified during iteration"
object(HH\Set)#3 (3) {
  int(100)
  int(200)
  int(300)
}
int(100)
object(HH\Set)#3 (3) {
  int(100)
  int(200)
  int(300)
}
int(200)
object(HH\Set)#3 (2) {
  int(100)
  int(200)
}
int(300)
string(40) "Collection was modified during iteration"

Note that in the example, the actual add or removal will take effect, but the exception will be thrown when we go back to the loop iteration check.

By Reference

You cannot do a foreach by reference with collections.

foreach ($vec as &$val)

This will result in a fatal error, not even an exception.

There is a way to mimic this behavior.

<?hh

namespace Hack\UserDocumentation\Collections\Iterating\Examples\Reference;

function run(): void {
  $arr = array ('A', 'B', 'C');
  // if $arr was a Vector instead, we would get a
  // Fatal error: Collection elements cannot be taken by reference
  var_dump($arr);
  foreach ($arr as &$val) {
    var_dump($val);
    if ($val === 'B') {
      $val = 'D';
    }
    var_dump($arr);
  }

  // How to mimic the above
  $vec = Vector {'A', 'B', 'C'};
  var_dump($vec);
  foreach ($vec as $key => $val) {
    var_dump($val);
    if ($val === 'B') {
      $vec[$key] = 'D';
    }
    var_dump($vec);
  }
}

run();
Output
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
string(1) "A"
array(3) {
  [0]=>
  &string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
string(1) "B"
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  &string(1) "D"
  [2]=>
  string(1) "C"
}
string(1) "C"
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "D"
  [2]=>
  &string(1) "C"
}
object(HH\Vector)#1 (3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
string(1) "A"
object(HH\Vector)#1 (3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
string(1) "B"
object(HH\Vector)#1 (3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "D"
  [2]=>
  string(1) "C"
}
string(1) "C"
object(HH\Vector)#1 (3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "D"
  [2]=>
  string(1) "C"
}

Basically, you explicitly pull out the key/value pair of the collection and then modify the value of the key explicitly.