loops goes insideMove that foreach() inside the method

Several PHP functions work with single or multiple values. For example, array_merge(), which merges several arrays into one. Or, isset(), which checks simultaneously the existence of several variables. str_replace() and preg_replace_callback() also come to mind. This is a good place to move that foreach() inside the method.

This approach usually brings a bit of performance, by processing a list of arguments at once, instead of calling multiple times the same method to process each element individually. This is called ‘Moving the foreach inside the method’. Let’s see how we can do it, and what performances it brings.

Moving the foreach in the method

The basic idea is to refactor the following code

<?php
foreach($array as &$element) {
     $object->foo($element);
}
?>

into

<?php
$object->foo($array);
?>

Let’s be clear : there is no magic happening here. The foreach() loop (or any other loop, indeed), is actually moved inside the foo() definition for local processing. The loop still exists after refactoring.

<?php
class X {
  function foo($element) {
    $this->array[] = $element;
  }
}
?>

may become

<?php
class X {
  function foo($element) {
    if (is_array($element)) {
      $this->array = array_merge($element);
      return;
    }
    
    // if element is not an array
    $this->array[] = $element;
  }
}
?>

Performances are in the function call

In fact, the gain in performances relies on two aspects : the reduction of function calls, and local optimisations.

Basically, when the loop is outside the method, there are as many calls to the method as there are elements in the array. PHP must create a new context each time, and clean it each time. This is very classic work, and it would be boring to us, humans, but PHP loves this.

This means that it is a micro-optimisation, which will shave off 1 ms for every 10k calls to a function, and may be 2 ms for a method call. So, deciding upon refactoring should weight the amount of work compared to the speed bump.

The second aspect is a local optimization. For example, merging arrays with array_merge(), aka en masse, is actually faster than pushing the elements one by one. Even better, merging the arrays only once would bring even more performances. This aspect totally depends on the business case at hand, so it is not always possible to extract performances there.

3 ways to calling methods with multiple values

single or array arguments

The first way is to have the argument to be a single value or an array. This is the initial illustration in the article. The values have to be collected into an array first, then they are send to the method as one argument. With type detection, the method will branch depending on the type of incoming argument.

This is convenient when the data is already provided as a list of consistent values. Reading from a JSON file, or extracting the data from the database are situations where the collection of values are already in place.

Argument to array conversion : variadic

PHP has the variadic operator, which, when used in a method signature, will collect all the last arguments into an array. This is a great way to hide the transformation of the arguments into an array. Moreover, it allows the typing of the arguments.

And even when the arguments are in an array, turning them into a list of argument is quite convenient.

<?php
class X {
  function foo(string ...$element) {
        // $element is always an array
      $this->array = array_merge($element);
      return;
  }
}

$objectX->foo(...$db_rows);
?>

Traditional func_get_args()

func_get_args() is the last way to bring all arguments into an array. It is a bit more cumbersome than variadic arguments, and lays the burden of typing or filtering the incoming data to the method itself.

Making the array version more visible

Moving the foreach() inside the method has a potential for both performances and convenience. For performances, this happens when the method is very frequently used, and the local optimization are obvious. For convenience, it is better when the arguments are already in an array format, even as a hash.

The main limitation is then the education of the developer/user. Even after years of documentation and articles, str_replace() and array_merge() are not often used with arrays. It is not obvious that some arguments may be simple, and others may be array(). Or how to dynamically provide arbitrary list of arguments.

curl_setopt() went another way, and made obvious the feature with curlsetoptarray() function, just like preg_replace_callback() and preg_replace_callback_array(). These functions appear as an obvious answer when one wonder how to manipulate a long list of arguments. str_replace_array() anyone?

So, when you see repeated calls so the same method in a row, it is probably time to ask if it is worth refactoring the foreach in the method.