PHP fun: regex builder in 8.5

Published: (January 8, 2026 at 05:06 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Why?

The main reason is that this is a builder that creates a text, so it shouldn’t need to instantiate an object.
The second reason is that the built‑in PHP regex functions should be used directly, instead of wrapping them in an object method:

$match = preg_match($pattern, 'test'); // instead of duplicates.IsMatch('test')

How will it work?

I’m not going to create the whole library; I’ll highlight a few possibilities of the C# library in their pipe‑operator form.

The start can be an empty string, a string with a delimiter, or even a delimiter function.

$pattern = '' |> anyCharacter(...); // regex: .*

// or

$pattern = '/' |> anyCharacter(...);

// or

$pattern = delimiter() |> anyCharacter(...);

The library uses class constants (e.g., Pattern.With.LowercaseLetter) to add known character patterns. In PHP this can be a backed enum, and the anyCharacter function can become any with an argument.

enum CharacterPattern: string
{
    case Any = '.';
    case LowercaseLetter = '[a-z]';
    case Word = '\w';
}

function any(string $pattern, CharacterPattern|string $add = CharacterPattern::Any): string
{
    $addPattern = $add instanceof CharacterPattern ? $add->value : $add;
    return "$pattern$addPattern*";
}

// examples
$pattern = '' |> fn($p) => any($p);
$pattern = '' |> fn($p) => any($p, CharacterPattern::LowercaseLetter);
$pattern = '' |> fn($p) => any($p, '[sunday|monday]');

For the last example the library needed a method literal; type hinting is more than enough.

To complete the quantifying pattern functions, we can add exact and atLeast functions.

Positive look‑ahead

The PositiveLookahead method can result in nested function calls. Splitting it into two functions keeps the builder flat.

function positiveLookaheadStart(string $pattern, string $times = ''): string
{
    return "$pattern(?=$times";
}

function positiveLookaheadEnd(string $pattern): string
{
    return "$pattern)";
}

// example
$pattern = ''
    |> fn($p) => positiveLookaheadStart($p, '.*')
    |> fn($p) => any($p, '[sunday|monday]')
    |> fn($p) => positiveLookaheadEnd($p);

Named groups and back references

A method like NamedGroup can be split into a group function that also handles non‑capturing groups. A back‑reference is simply \1 or \k.

function backReference(string $pattern, int|string $add): string
{
    $reference = is_string($add) ? "k" : $add;
    return "$pattern\\$reference";
}

Conclusion

The benefit of using the pipe operator is that there will be less code because each function does one thing without side effects. This also means fewer edge cases to test, as the regex building happens at the language level.

A minor downside is that generic function names may be less intuitive than a fluent API. To mitigate this, you can add your own descriptive functions, making the library easily extensible.

Next time you consider the builder pattern, ask yourself whether the pipe operator might be a better fit.

PS: Don’t use a regex builder in production; use the generated pattern from a pre‑warm cache instead.

Back to Blog

Related posts

Read more »

Introduction to Dev.to API

Getting Started - Log in to your dev.to account. - Go to Settings → Account. - Scroll down to the DEV API Keys section. - Generate a new key and copy it somewh...

My NewbieDevDiary #0000

Cleaned‑up Markdown markdown !Forem Logohttps://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-upload...

PageSpeed 70 vs 95: the true reality

Introduction Let’s be honest from the start: if you have a website for an accounting firm, a psychologist, a real estate agency, a barbershop, a clinic, an off...