foreach не трогает массив во время прохода по нему, т.е. можно использовать вложенные циклы по одному и тому же массиву (а так же можно пользоваться current/begin/end для этого у массива отдельная переменная состояния).
Рекомендация, не удалять элементы массива внутри его же перебора (лучше собирать идентификаторы в отдельный массив на_удаление и удалять после или использовать array_filter).
Еще есть не очевидный момент, foreach($mas as $k=>$v) возвращает копию элемента $v, но поведение будет зависеть от типа этого элемента (это не foreach такой это в основе php передача по копированию идет так), например если элемент это массив, $v будет копией массива (но его элементы-объекты будут переданы все еще по ссылке), и изменение в нем не отразится на исходном, а вот если элемент массива будет объектом (экземпляр объекта) то значение его полей будет изменено. Это 'странное' поведение можно детерминировать, определив элемент в итераторе по ссылке &:
$mas=[[1,2],[2,3]];
foreach($mas as $k=>$v) $v[0]=0;
echo json_encode($mas)."\n";
// [[1,2],[2,3]]
$mas=[(object)["a"=>1],(object)["a"=>2]];
foreach($mas as $k=>$v) $v->a=0;
echo json_encode($mas)."\n";
// [{"a":0},{"a":0}]
$mas=[[1,(object)["a"=>1]],[2,(object)["a"=>2]]];
foreach($mas as $k=>$v) $v[1]->a=0;
echo json_encode($mas)."\n";
// [[1,{"a":0}],[2,{"a":0}]] смотрим что атрибут объектов внутри массива все же изменился!
// по ссылке
$mas=[[1,2],[2,3]];
foreach($mas as $k=>&$v) $v[0]=0;
unset($v); // об этом приходится помнить, что бы не получить нежданчик, если после цикла сделать $v=10 изменит последний элемент массива $mas
echo json_encode($mas)."\n";
// [[0,2],[0,3]]
вроде бы бездумное использование $k=>&$v (всегда по ссылке) считается вредным советом, ломает оптимизацию 'интерпретатора' и всегда нужно помнить делать unset после цикла, что бы не случайно не воспользоваться $v, ведь она будет ссылкой на последний элемент перебираемого массива.