DEV Community

Cover image for A real world case of recursion done right in PHP
Thierry Feuzeu
Thierry Feuzeu

Posted on

A real world case of recursion done right in PHP

Nick Cosentino @DevLeaderCa posted this message about recursion on X, ex Twitter, in October 2025, which received over 460 replies.

I quote an excerpt: I've found that converting over to an iterative loop based approach is almost always more readable and easier to debug. He explains that in over 20 years of programming, he has never used recursion, thus questioning its usefulness.

In this article, I show a real case where I used recursion, and where I think that it is much simpler than its iterative counterpart.

But before going any further, let see what makes a recursive algorithm powerful.

The Fibonacci sequence

To understand the value of recursion, let's look at the implementations with and without recursion of an algorithm very often used to illustrate it: the Fibonacci sequence.

Here's the recursive algorithm.

if n = 0 or n = 1 then
    fib(n) = 1
else
    fib(n) = fib(n − 1) + fib(n − 2)
endif
Enter fullscreen mode Exit fullscreen mode

And here's the iterative one.

if n = 0 or n = 1 then
    fib(n) = 1
else
    a = 1
    b = 1
    for i in 2 .. n do
        c = a + b
        a = b
        b = c
    endfor
    fib(n) = c
endif
Enter fullscreen mode Exit fullscreen mode

This simple example demonstrates the advantages of recursion: no loops and no intermediate variables.

This also gives us an indication of when recursion is advantageous, namely when the function can be defined with a simple expression depending on itself. This might not be the case for example when the data being processed is a graph instead of a tree.

Our problem

The library we'll be talking about is the PHP UI Builder. It builds application UIs using components, as defined by CSS frameworks. It provides a common API which defines the components, and from which specialized libraries can generate HTML code for a given CSS framework, like Bootstrap, Bulma or Tailwind CSS.

The UI components

With its hierarchical component structure, a UI is particularly well-suited to a recursive algorithm. A component has properties, but can also contain components that themselves have some properties and can contain other components, and so on.

The virtual components

When building a UI from data, the result can sometimes vary depending on their values. Some components will only be created under certain conditions, others are multiplied according to the entries in a list or loop, and others may be an aggregate of different components.

To represent these cases, our library introduces the concept of virtual components. These are components that take a set of data as input and produce as output a set of zero or more components, which may or may not be virtual.

There are 4 of them:

  • When: takes a condition as input and generates a component if it is met.
  • Each: takes an array of data as input and generates a component for each element.
  • Pick: takes a list of conditions as input and generates a different component depending on the first one that is true.
  • List: aggregates any set of components into one.

A UI will ultimately consist of a tree structure of components, each of which can be virtual or real. A real component can have several children, each of which can be virtual or real, and a virtual component generates a list of components, each of which can be virtual or real.

Here's an example of a function using the UI library.

/**
 * @param array $collations
 *
 * @return string
 */
public function addDbForm(array $collations): string
{
    return $this->ui->build(
        $this->ui->form(
            $this->ui->row(
                $this->ui->col(
                    $this->ui->label($this->ui->text('Name'))
                        ->setFor('name')
                )->unit(1, 4),
                $this->ui->col(
                    $this->ui->input()
                        ->setType('text')
                        ->setName('name')
                )->unit(1, 2)
            ),
            $this->ui->when(count($collations) === 0, fn() =>
                $this->ui->input()
                    ->setType('hidden')
                    ->setName('collation')
                    ->setValue('')),
            $this->ui->when(count($collations) > 0, fn() =>
                $this->ui->row(
                    $this->ui->col(
                        $this->ui->label($this->ui->text('Collation'))
                            ->setFor('collation')
                    )->unit(1, 4),
                    $this->ui->col(
                        $this->ui->select(
                            $this->ui->option('(collation)')
                                ->setValue('')
                                ->selected(true),
                            $this->ui->each($collations, fn($_collations, $group) =>
                                $this->ui->pick(
                                    $this->ui->when($group === '', fn() =>
                                        $this->ui->each($_collations, fn($collation) =>
                                            $this->ui->option($collation)
                                        )
                                    ),
                                    $this->ui->when(true, fn() =>
                                        $this->ui->optgroup(
                                            $this->ui->each($_collations, fn($collation) =>
                                                $this->ui->option($collation)
                                            )
                                        )->setLabel($group)
                                    )
                                )
                            )
                        )->setName('collation')
                    )->width(6)
                )
            )
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

The processing algorithm

To generate a UI from a component tree, we will use the following algorithm:

function build(c)
    children = []
    expand(c, children)

    c.elements = []
    for child in children
        if child is a component then
            build(child)
            append child.elements to c.elements
        else
            append child to c.elements
        endif
    endfor
endfunction
Enter fullscreen mode Exit fullscreen mode
function expand(c, children)
    if c is virtual then
        for child in c.children
            expand(child, children)
        endfor
    else
        append c to children
    endif
endfunction
Enter fullscreen mode Exit fullscreen mode

What we have here is actually a combination of two recursive functions.

The expand() function returns the list of real components generated by a virtual component. It will therefore iterate recursively over all its children as long as virtual components are present.

The build() function, which is the entry point for the process, first calls the expand() function. Its list of children then consists only of real components, on which it executes recursively.

The final result is in the Scope class. In their PHP implementation, the recursive expand() function is 15 lines of code long, and the build() function is 40.

Two extremely simple functions, which form the basis that allows the implementation in a single library of both the templating functionalities of libraries such as Twig or Blade, and the HTML code generation of libraries such as Spatie's HTML Builder or Symfony's Forms component (the rendering part).

The UI library is used to build the Jaxon DbAdmin UI, demonstrating that it allows the construction of complex UIs, calling classes and their methods instead of writing HTML templates.

An overview of the Jaxon DbAdmin UI

The limitations of recursion

Nick Cosentino's message rightly points out two drawbacks of recursion, which we will briefly discuss here.

Debugging

First, it's absolutely true that recursion is difficult to debug, particularly because mentally unraveling the instructions executed by a recursive algorithm is very complex. However, at the same time, the simplicity of recursive algorithms leaves little chance for a bug to occur.

Therefore, before choosing recursion, it's important to ensure that the algorithm being implemented can be expressed very simply in a recursive manner. This is indeed the case for the Fibonacci sequence and our library.

Memory usage

Implementing a recursive algorithm can consume a significant amount of memory, depending on the level of nesting of the calls.
For our UI library, the level of nesting of an application's UI components will always be very limited, and even more if it is built one part at a time, each in a separate recursive function call.

Moreover, PHP imposes a limit on the number of nested calls within a recursive function, and the consequence of an overflow will be to fail a single call, not to crash the entire server.

The closing word

To answer Nick Cosentino's initial question, of course it is possible to create all sorts of complex software without using recursion.

However, this doesn't mean it can always be replaced by an iterative version, but rather that cases where one can fully leverage all its advantages are not very common.

Top comments (0)