vincentvanwijk/fluent-regex

A package to fluently create regular expressions

Maintainers

Package info

github.com/VincentVanWijk/fluent-regex

Homepage

pkg:composer/vincentvanwijk/fluent-regex

Statistics

Installs: 8

Dependents: 0

Suggesters: 0

Stars: 7

Open Issues: 2

v0.1.0 2026-02-06 14:32 UTC

README

A powerful Laravel package that provides an elegant, fluent interface for building regular expressions in PHP. Say goodbye to cryptic regex syntax and hello to readable, maintainable pattern matching.

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status PHPStan codecov Total Downloads

Table of Contents

Features

Readable Syntax - Write regex patterns using intuitive, chainable methods 🔒 Automatic Escaping - Special characters are escaped automatically 🎯 Type Safety - Full IDE autocomplete support and type hints 🧪 Thoroughly Tested - Comprehensive test coverage 🎨 Laravel Integration - Seamless integration with Laravel applications ⚡ Full Regex Support - Complete implementation of PHP regex features including:

  • Named and numbered capture groups with backreferences
  • Lookahead and lookbehind assertions (positive and negative)
  • Atomic groups and possessive quantifiers
  • Conditional patterns and recursion
  • POSIX character classes
  • Mode modifiers (case-insensitive, multiline, etc.)
  • Unicode and hexadecimal character support

Requirements

  • PHP 8.1 or higher
  • Laravel 9.0 or higher

Installation

Install the package via Composer:

composer require vincentvanwijk/fluent-regex

Optionally, publish the configuration file:

php artisan vendor:publish --tag="fluent-regex-config"

Quick Start

use VincentVanWijk\FluentRegex\Facades\FluentRegex;

// Create a regex to match an email address
$regex = FluentRegex::create('contact@example.com')
    ->alphaNumeric()->oneOrMoreTimes()
    ->exactly('@')
    ->letter()->oneOrMoreTimes()
    ->exactly('.')
    ->letter()->betweenNTimes(2, 4);

// Test the pattern
if ($regex->test()) {
    echo "Valid email!";
}

// Get the regex string
echo $regex->get(); // /[a-zA-Z0-9]+@[a-zA-Z]+\.[a-zA-Z]{2,4}/mu

Documentation

Character Matching

Basic Matching

// Match exact strings (automatically escaped)
->exactly('hello')              // hello
->exactly('regex!')             // regex\!

// Match any single character
->anyCharacter()                // .

// Match specific character sets
->anyCharacterOf('abc')         // [abc]
->anyCharacterOf('0-9')         // [0\-9]

// Negated character sets (using not modifier)
->not->anyCharacterOf('abc')    // [^abc]

Letter & Number Matching

->letter()                      // [a-zA-Z]
->lowerCaseLetter()             // [a-z]
->upperCaseLetter()             // [A-Z]
->digit()                       // \d (equivalent to [0-9])
->not->digit()                  // \D (non-digit)
->alphaNumeric()                // [a-zA-Z0-9]
->hexDigit()                    // [0-9A-Fa-f]

Character Ranges

->range('a', 'z')               // [a-z]
->range('0', '9')               // [0-9]
->not->range('A', 'Z')          // [^A-Z]

Quantifiers

Quantifiers specify how many times a pattern should match. All quantifiers support lazy and possessive modes.

// Basic quantifiers
->zeroOrOneTime()               // ?
->zeroOrMoreTimes()             // *
->oneOrMoreTimes()              // +

// Specific counts
->nTimes(3)                     // {3}
->nTimesOrMore(3)               // {3,}
->betweenNTimes(3, 6)           // {3,6}

// Quantifiers with strings
->zeroOrOneTime('a')            // a?
->oneOrMoreTimes('x')           // x+
->nTimesOf('b', 5)              // b{5}

// Lazy quantifiers (match as few as possible)
->lazy->oneOrMoreTimes()        // +?
->lazy->zeroOrMoreTimes()       // *?

// Possessive quantifiers (no backtracking)
->possessive->oneOrMoreTimes()  // ++
->possessive->zeroOrMoreTimes() // *+

Anchors & Boundaries

// Line and string anchors
->startOfLine()                 // ^ (affected by multiline mode)
->endOfLine()                   // $ (affected by multiline mode)
->startOfString()               // \A (not affected by multiline)
->endOfString()                 // \Z (not affected by multiline)
->absoluteEndOfString()         // \z (strict end, no trailing newline)

// Word boundaries
->wordBoundary()                // \b
->nonWordBoundary()             // \B

Groups & Captures

Capture Groups

// Numbered capture group
->captureGroup(function ($regex) {
    return $regex->digit()->oneOrMoreTimes();
})  // (\d+)

// Named capture group
->namedCaptureGroup('year', function ($regex) {
    return $regex->digit()->nTimes(4);
})  // (?<year>\d{4})

// Non-capturing group
->nonCaptureGroup(function ($regex) {
    return $regex->exactly('http')->zeroOrOneTime('s');
})  // (?:https?)

Advanced Groups

// Atomic group (prevents backtracking)
->atomicGroup(function ($regex) {
    return $regex->digit()->oneOrMoreTimes();
})  // (?>\d+)

// Conditional pattern
->conditional(1,
    function ($regex) { return $regex->exactly('yes'); },
    function ($regex) { return $regex->exactly('no'); }
)  // (?(1)yes|no)

// Recursion
->recurse()                     // (?R) - recurse entire pattern
->recurseGroup(1)               // (?1) - recurse group 1

Backreferences

Reference previously captured content:

// Match repeated words: "the the" or "is is"
FluentRegex::create('the the')
    ->captureGroup(fn($r) => $r->wordCharacter()->oneOrMoreTimes())
    ->whiteSpace()
    ->backreference(1);         // \1

// Using named backreferences
FluentRegex::create('hello hello')
    ->namedCaptureGroup('word', fn($r) => $r->letter()->oneOrMoreTimes())
    ->whiteSpace()
    ->namedBackreference('word');  // \k<word>

Lookahead & Lookbehind

Assert patterns without consuming characters:

// Positive lookahead - assert that pattern follows
->positiveLookAhead(function ($regex) {
    return $regex->digit();
})  // (?=\d)

// Negative lookahead - assert that pattern does NOT follow
->negativeLookAhead(function ($regex) {
    return $regex->whiteSpace();
})  // (?!\s)

// Positive lookbehind - assert that pattern precedes
->positiveLookBehind(function ($regex) {
    return $regex->exactly('$');
})  // (?<=\$)

// Negative lookbehind - assert that pattern does NOT precede
->negativeLookBehind(function ($regex) {
    return $regex->digit();
})  // (?<!\d)

Character Classes

Meta Sequences

->whiteSpace()                  // \s (space, tab, newline)
->nonWhiteSpace()               // \S
->wordCharacter()               // \w ([a-zA-Z0-9_])
->not->wordCharacter()          // \W
->uniCodeCharacter()            // \X (any Unicode sequence)

POSIX Character Classes

->punctuation()                 // [[:punct:]]
->blank()                       // [[:blank:]] (space and tab only)
->controlCharacter()            // [[:cntrl:]]
->graphicCharacter()            // [[:graph:]] (visible characters)
->printableCharacter()          // [[:print:]] (visible + spaces)

Special Characters & Tokens

// Whitespace characters
->newLine()                     // \R (any newline sequence)
->lineFeed()                    // \n
->carriageReturn()              // \r
->tabCharacter()                // \t
->verticalTab()                 // \v
->formFeed()                    // \f

// Special characters
->nullCharacter()               // \0
->alarm()                       // \a (bell)
->escapeCharacter()             // \e

// Character by code
->hexCharacter('41')            // \x41 (matches 'A')
->unicodeCharacter('00A9')      // \u00A9 (matches ©)
->controlCharacter('C')         // \cC (Ctrl+C)

// Alternation
->or('cat', 'dog', 'bird')      // cat|dog|bird

// Comments (ignored by regex engine)
->comment('Match the domain')   // (?#Match the domain)

// Raw regex (for advanced users)
->raw('[a-z]{2,}')              // [a-z]{2,}

Mode Modifiers

Change regex behavior inline:

// Case sensitivity
->caseInsensitive()             // (?i)
->caseSensitive()               // (?-i)

// Dot behavior
->dotMatchesNewlines()          // (?s) - dot matches any character including newlines
->dotExcludesNewlines()         // (?-s)

// Multiline mode
->inlineMultilineMode()         // (?m)
->disableInlineMultilineMode()  // (?-m)

// Free-spacing mode (ignore whitespace, allow comments with #)
->freeSpacingMode()             // (?x)
->disableFreeSpacingMode()      // (?-x)

// Multiple modifiers at once
->setModeModifiers('im')        // (?im)
->setModeModifiers('i', 's')    // (?i-s)

Substitution & Replacement

$regex = FluentRegex::create('Hello World');

// Basic replacement
$result = $regex->digit()->replace('X');

// Replace with backreferences
$result = $regex->captureGroup(fn($r) => $r->wordCharacter()->oneOrMoreTimes())
    ->replace('[$1]');  // Wrap each word in brackets

// Replace with callback
$result = $regex->wordCharacter()->oneOrMoreTimes()
    ->replaceCallback(fn($matches) => strtoupper($matches[0]));

// Replace and get count
['result' => $text, 'count' => $n] = $regex->digit()->replaceWithCount('X');

// Split by pattern
$parts = $regex->whiteSpace()->oneOrMoreTimes()->split();

// Test if pattern matches
if ($regex->digit()->test()) {
    echo "Contains digits!";
}

Modifiers

Modifiers temporarily change behavior for the next method call:

The not Modifier

Negates character classes:

->not->letter()                 // [^a-zA-Z]
->not->digit()                  // \D
->not->anyCharacterOf('aeiou')  // [^aeiou]

The lazy Modifier

Makes quantifiers match as few characters as possible:

->lazy->oneOrMoreTimes()        // +?
->lazy->zeroOrMoreTimes()       // *?
->lazy->betweenNTimes(2, 5)     // {2,5}?

The possessive Modifier

Makes quantifiers possessive (no backtracking):

->possessive->oneOrMoreTimes()  // ++
->possessive->zeroOrMoreTimes() // *+

Note: Modifiers automatically reset after each method call.

Configuration

Configure default behavior in config/fluent-regex.php:

return [
    // Delimiter used at the start and end of the regex
    'delimiter' => '/',

    // Multiline mode: ^ and $ match line boundaries (not just string boundaries)
    'multiLineMode' => true,

    // Unicode mode: pattern strings are treated as UTF-16
    'unicodeMode' => true,
];

You can override these per-instance:

$regex = FluentRegex::create('test', '#')  // Custom delimiter
    ->disableMultilineFlag()
    ->disableUnicodeFlag();

Advanced Usage

Email Validation

$email = FluentRegex::create('user@example.com')
    ->startOfString()
    ->alphaNumeric()->oneOrMoreTimes()
    ->anyCharacterOf('._-')->zeroOrMoreTimes()
    ->exactly('@')
    ->alphaNumeric()->oneOrMoreTimes()
    ->exactly('.')
    ->letter()->betweenNTimes(2, 4)
    ->endOfString();

if ($email->test()) {
    echo "Valid email!";
}

URL Matching

$url = FluentRegex::create($input)
    ->startOfString()
    ->exactly('http')
    ->exactly('s')->zeroOrOneTime()
    ->exactly('://')
    ->nonCaptureGroup(fn($r) =>
        $r->alphaNumeric()->oneOrMoreTimes()
            ->anyCharacterOf('.-')->zeroOrMoreTimes()
    )
    ->oneOrMoreTimes()
    ->endOfString();

Phone Number Extraction

$phone = FluentRegex::create($text)
    ->nonCaptureGroup(fn($r) => $r->digit()->nTimes(3))
    ->anyCharacterOf('-.')->zeroOrOneTime()
    ->digit()->nTimes(3)
    ->anyCharacterOf('-.')->zeroOrOneTime()
    ->digit()->nTimes(4);

$matches = $phone->matchAll();

Password Validation with Lookaheads

// At least 8 chars, one uppercase, one lowercase, one digit
$password = FluentRegex::create($input)
    ->startOfString()
    ->positiveLookAhead(fn($r) => $r->anyCharacter()->zeroOrMoreTimes()->upperCaseLetter())
    ->positiveLookAhead(fn($r) => $r->anyCharacter()->zeroOrMoreTimes()->lowerCaseLetter())
    ->positiveLookAhead(fn($r) => $r->anyCharacter()->zeroOrMoreTimes()->digit())
    ->anyCharacter()->nTimesOrMore(8)
    ->endOfString();

Creating from File

$regex = FluentRegex::createFromFile('/path/to/file.txt')
    ->exactly('search term')
    ->match();

Testing

Run the test suite:

composer test

Run tests with coverage:

composer test-coverage

Run static analysis:

composer analyse

Fix code style:

composer format

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Security

If you discover any security-related issues, please email vincentvanwijk@hotmail.nl instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.