Release 0.9.8 - foreach baby, foreach!


Up to this point, you could either use T-Regx methods that return array, in order to iterate them, or use one of the collection methods map(), forEach(), filter(), etc.

Right now any chainable method in T-Regx is also iterate

foreach (pattern('\d+')->match('') as $match) {
foreach (pattern('\d+')->match('')->asInt() as $digit) {
foreach (pattern('\d+')->match('')->all() as $text) {

Shorthand method

In addition to previous release, when we added asArray() method, we also added a shorthand get() method for capturing groups.

pattern('(origin/)?master')->match('master')->first(function (Match $match) {
$match->get(1); // same as $match->group(1)->text();

Release 0.9.7 - Match as vanilla array!

There was a lot of changes in the code, so I reckon we could release twice in the same week, because why not :)

So what are the changes?

Bare with me.

The concept

Capturing groups in T-Regx have a really rich API (probably the richest out there), with a lot of variables. Most importantly T-Regx handles:

  • Invalid groups (e.g. negative index -1 or malformed group !@#$), which always throw exception
  • Missing groups (e.g. group 4 used in pattern, that only has 2 groups; same for named) which conditionally throws exceptions
  • Optional groups (e.g. (origin/)?master), which is really tricky to distinguish with PCRE
  • Matched groups (which can be tricky, if the matched group is an empty string "")

Because of that, syntax of groups is not the shortest:

pattern('(origin/)?master')->match('master')->first(function (Match $match) {
$match->group(1)->text(); // for example

But we know that T-Regx users mostly care about the last group, Matched groups, so they would like to use them with as simple syntax as possible. That makes sense.

The idea

At first, there was an idea of Match details implementing \ArrayAccess, so this syntax would be possible:

pattern('(origin/)?master')->match('master')->first(function (Match $match) {
$match[1]; // same as above

Well, that syntax does look good, at first, but it comes at a price. A high price.

Why we ditched the \ArrayAccess idea:

  • Unnecessary set and unset methods
  • Methods that work for arrays (array_key_exist()) won't work with \ArrayAccess
  • empty($match[1]) returns true, even if the group 1 was matched ("" and "0" yes is falsy)
  • isset($match[-2]) couldn't throw an exception for a malformed group
  • There's a bug in PHP, that causes $match['100'] to be treated as $match[100] (cast to int any numeric value).

The solution

So, instead, we got an idea: What if $match was a real PHP array. Every method or notation that works for arrays, will also work.

pattern('(origin/)?master')->match('master')->asArray()->first(function (array $match) {
$match[1]; // same as above

The structure of the array is perfectly identical to what preg_match() would return :)

Release 0.9.6 - First/all changes!

Another release ahead of us. This one is about T-Regx chainable interface. We made sure, that first() chained with anything always uses preg_match(), instead of preg_match_all().

For example:

->flatMap(function (Match $match) {
return [$match->text() => $match->offset()];
->groupByCallback(function (string $text) {
return $text[0];

this code will now use preg_match(). Really, this whole release was about ensuring that.

The only exception is filter(), for which it would be really wasteful to call preg_match() first, and then, if the filter has failed, call preg_match_all().

In the future releases we'll make sure that the exception for unmatched first() are also uniform (probably SubjectNotMatchedException, instead of NoSuchFluentElementException).

Boy, are design patterns so cool for this kind of job ;D

Toss a coin to your T-Regx!

Hello, back again! :) We've added a "Sponsor" button on


If you like T-Regx going in the right direction, now you have the opportunity to throw us buck or two.

Next release

And a heads up, in the new 0.9.6 release, we'll add a really smart asInt() and fluent() methods; which are already present, but will get an upgrade.

You see, match()->first() calls preg_match() and that makes sense. Also match()->fluent() calls preg_match_all(), because later fluent()->map() or fluent()->filter()->first() can be called, for example. And that also, sorta makes sense. But, unfortunately match()->fluent()->first() and match()->asInt()->first() also call preg_match_all(), and that's a bit wasteful.

So now we're introducing a change (similar to Java 8 Streams) that will call preg_match() for fluent()->first() and asInt()->first().

Release 0.9.5 - Alternation in prepared patterns!

This release brings alternation in prepared patterns!

Up to this point, there was no reasonable way to create a pattern from a variable number of inputs, for example you allow your users to input 0, 1 or more tags, which later should be used in a pattern. In procedural world, probably array_map() with preg::quote() would do the job, but wait! You don't have to code, it's already here:

Pattern::bind('^user:@id/findBy:@tags/all$', [
'id' => $user->id,
'tags' => $_GET['tags']

In other words:

Pattern::bind('My tag is: "@tags"', ['tags' => ['one', 'two', 'three']]);

creates a pattern:

/My tag is: "(one|two|three)"/

Rest assured:

  • the values are quoted with preg::quote(), to protect you from malicious code
  • the group is non-capturing (use 'My tag is: "(@tags)"' for a capturing group, to be used with group())

The alternation is really smart too - if you use i or u flag, T-Regx will perform certain optimization, for example:

Pattern::inject('Find: @ :)', [['foo', 'bar', 'FOO']], 'i');

then it wil collapse foo and FOO, since i flag is used:

/Find: (foo|bar) :)/

That's it in this release! Stay tuned :)

Release 0.9.4 - Exception changes and groupBy()

This release brings updates in exceptions (namespaces, new detailed exceptions) and a groupBy() method.


In previous release we renamed SafeRegexException to PregException. In this, we're renaming CleanRegexException to PatternException. So now, those two general exceptions sync nicely with their base methods:

try {
return preg::match('/Foo/', $subject);
} catch (PregException $e) {
try {
return pattern('Foo')->test($subject);
} catch (PatternException $e) {

They both extend RegexException - base for all exceptions thrown by T-Regx. So that's the first thing.

The second exception update - previously, every exception thrown based on preg_last_error() method was RuntimePregException. Now, each error has a dedicated exception, which can be caught separately:

try {
return preg::match($pattern, $subject);
} catch (BacktrackLimitPregException $exception) {
} catch (Utf8OffsetPregException $exception) {

The detailed list of changes is in

New method groupBy()

This release also comes with a brand new method - groupBy() which groups matches by a capturing group (name or index). It can match strings, offsets and also map them with map() and flatMap(). Additionally, it can be chained with filter() to leave out unwanted matches:

return pattern('(\d)(?<unit>cm|mm)')->match($strings)
->filter(function (Match $match) {
return $match->group(1)->toInt() % 2 == 0;
->map(function (Match $match) {
return $match->group(1)->toInt() * 100;

Release 0.9.3 - Minor changes

That's a really quick update, we just found a PHP bug report #78853, that's probably going to be fixed in future versions of PHP, but we decided to include the bugfix in T-Regx so it's fixed in PHP 7.1 (if you're using T-Regx :)

Apart from that, we added preg::last_error_msg(), which returns the same data as preg::last_error(), but in in human-readable string. There are suggestions on PHP source code, that this method might become part of standard PHP library, so we at T-Regx thought we might include it already :) And if it's not included, after all, that's still a good idea to have this method :)

And there are some breaking changes, that should increase readability, for example this exception:

try {
return preg::match('/pattern/', $word)
catch (PregException $e) {

seems a bit more intuitive than

catch (SafeRegexException $e) {

So to name the exception based on a class/method, not the module :)

And FIY, the next feature we're working at T-Regx is groupBy(), which should complete groupByCallback() functionality already present in T-Regx. So expect that method first! :)

That's it for today, thanks guys!

Release 0.9.2 - You're in for a treat!

๐Ÿ˜Ž T-Regx The Dinosaur is really proud to announce its first beta version! Despite the beta suffix, it's 100% suitable for production use. It doesn't have any known bugs - check out the issues. There is a few breaking changes (since that's a 0.* version), but there are also a looot of improvements and new feautres. What's new in this release:

Here's a release on github: (see

  • Breaking changes
    • Methods pattern()/Pattern::of() no longer "magically" guess whether a pattern is delimited or not. Pattern::of() assumes pattern is delimited, new Pattern::pcre() takes an old-school delimited pattern.
    • Constructor new Pattern() is no longer a part of T-Regx API. Use Pattern::of()/pattern()
    • Renamed Match.parseInt() to Match.toInt() (the same for MatchGroup)
    • Removed pattern()->match()->test()/fails(). From now on, use pattern()->test()/fails()
    • Removed is():
      • is()->delimited()
      • is()->usable()
      • is()->valid() is changed to valid()
    • Removed split()->ex(), changed split()->inc() to split()
  • Features
    • Added ๐Ÿ”ฅ
    • Added pattern()->match()->fluent() ๐Ÿ”ฅ
    • Added pattern()->match()->asInt()
    • Added pattern()->match()->distinct() (leaves only unique matches)
    • Added prepared pattern method Pattern::inject()/Pattern::bind() (see below)
    • In pattern()->match()->groups():
      • Added groups()->forEach()/iterate()
      • Added groups()->flatMap()
      • Added groups()->map()
      • Added group()->fluent()
      • Added groups()->names() (and namedGroups()->names())
      • Added groups()->count() (and namedGroups()->count())
    • Added match()->offsets()->fluent()
    • Added match()->group(string)->offsets()->fluent()
    • Added pattern()->forArray()->strict() which throws for invalid values, instead of filtering them out
  • SafeRegex
    • Added preg::grep_keys() ๐Ÿ”ฅ, that works exactly like preg::grep(), but filters by keys (also accepts PREG_GREP_INVERT)
  • Enhancements/updates
    • Method by()->group()->orElse() now receives lazy-loaded Match, instead of a subject
    • Added withReferences() to CompositePattern.chainedReplace()
    • Previously named Pattern::inject() is renamed to Pattern::bind()
    • The Pattern::bind() (old Pattern::inject()) still accepts values as an associative array, but new Pattern::inject() receives values without regard for the keys.
    • Fixed passing invalid types to forArray(). Previously, caused fatal error due to internal preg_grep() implementation.
  • Other
    • Now MalformedPatternException is thrown, instead of CompileSafeRegexException, when using invalid PCRE syntax.
    • Returning Match from replace()->callback() (instead of Match.text() as string)
    • Match +12 is no longer considered a valid integer for isInt()/toInt()
    • Unnamed group will be represented as null in Match.groupNames(), instead of being simply ignored
    • helper() method, Pattern and PatternBuilder now return interface PatternInterface, instead of Pattern class. Pattern class now only holds static utility methods, and PatternImpl holds the pattern implementation.
  • Maintenance

Foot note:

  • Apart from PHP type hints, every version up to this point could be run on PHP 5.3 (if one removes type hints from code, one can run T-Regx on PHP 5.3). Every error, exception, malfunction, inconsistency was handled correctly by T-Regx. From this version on (0.9.2), handling of the errors and inconsistencies is dropped, since T-Regx now only supports PHP 7.1.

Hope you guys will like and enjoy it!

Don't forget about T-Regx

Some of you might have noticed that not many new features have been added to 0.9.2 release candidate, since few weeks. But rest assured! :D Work on T-Regx hasn't slowed a bit! We've just put more effort on a more side-part of T-Regx.

Thanks to the keen eye of T-Regx user, Andreas Leathley, it stroke us that phpDoc in public methods of preg class (so preg::match(), preg::replace(), etc.) were not only outdated, they were incorrect at times. Thanks Andreas! :)

At first, we only wanted to correct the mistakes, but I quickly realized that mere phpDoc inherited from preg_*() methods is not enough :/ That could be easily found on, what T-Regx users could really benefit from is a really extensive, rich, long, detailed and robust documentation.

That's why we've spent a little over a week to create an application that's capable of generating a complex and rich phpDoc for preg class (of course we'll use it for more classes in T-Regx). Main updates for the documentation will include:

  • Robust description of every parameter of every preg_*()
  • Detailed description of every flag used (e.g. PREG_OFFSET_CAPTURE)
  • Rich description of each method, it's relation, similarities and differences to other methods

And it will be much easier to maintain, because every class will have a phpDoc that's not written, but generated.

I'm pretty certain T-Regx is going to have a documentation you've never seen before :D

Of course, there are also some minor changes:

  • We've added pattern()->match()->group()->fluent()
  • Added callback for pattern()->match()->group()->first()
  • Updated detailed description in

Keep looking forward to T-Regx 0.9.2! :)

The best Stargazers' Revolutions

I have imagined this moment for a long time. Is it real? Could it be T-Regx got 60 stars ? :D

But what's new.

There is preg_grep() function for filtering an array by a pattern, unfortunately there's no flag or another method to filter it by keys. There is a T-Regx method forArray()->filterByKeys(), but there was no such solution in SafeRegex.

That's why we added preg::grep_keys, which works exactly like preg_grep() but filters an array by keys, instead of values. It's going to be release in 0.9.2.

List of all changes incoming in 0.9.2 is available in, with fluent(), unique(), in-place replacements and more.