Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time

T-Regx Changelog


  • Soon

Added in 0.36.0

  • Breaking changes
    • Removed previously deprecated feature of tracking subject modification in replacement
      • Removed previously deprecated ReplaceGroup.modifiedSubject()
      • Removed previously deprecated ReplaceGroup.modifiedOffset()
      • Removed previously deprecated ReplaceGroup.byteModifiedOffset()
      • Removed previously deprecated ReplaceGroup
    • Removed ReplaceDetail.limit()
    • Removed ReplaceDetail

Added in 0.35.0

  • Breaking changes
    • Renamed MatchPattern to Matcher
    • Revoke certain classes as parts of public library API:
      • MatchedGroup is no longer part of public API, use Group instead.
      • NotMatchedGroup is no longer part of public API, use Group instead.
      • MatchDetail is no longer part of public API, use Detail instead.
      • Intable is no longer part of public API, return int instead.
    • Simplified \TRegx\CleanRegex\Match namespace
      • Moved Detail to \TRegx\CleanRegex\Match\Detail
      • Moved Group to \TRegx\CleanRegex\Match\Group
      • Moved Structure to \TRegx\CleanRegex\Match\Structure
      • Moved Element to \TRegx\CleanRegex\Match\Element

Added in 0.34.2

  • Deprecation
    • Deprecated keeping track of modified subjects
      • Deprecated ReplaceDetail.modifiedSubject()
      • Deprecated ReplaceDetail.modifiedOffset()
      • Deprecated ReplaceDetail.byteModifiedOffset()
      • Deprecated ReplaceGroup.modifiedSubject()
      • Deprecated ReplaceGroup.modifiedOffset()
      • Deprecated ReplaceGroup.byteModifiedOffset()
    • Deprecated replace by map and with group
      • Deprecated replace().by().group()
      • Deprecated replace().by().group().orElseWith()
      • Deprecated replace().by().group().orElseThrow()
      • Deprecated replace().by().group().orElseCalling()
      • Deprecated replace().by().group().orElseIgnore()
      • Deprecated replace().by().group().orElseEmpty()
      • Deprecated replace().by().group().map()
      • Deprecated replace().by().group().mapIfExists()
      • Deprecated replace().by().map()
      • Deprecated replace().by().mapIfExists()
    • Deprecated ReplaceDetail. It will be moved to Internal/ namespace in the future release and won't be considered part of public T-Regx API anymore.
    • Deprecated ReplaceGroup. It will be moved to Internal/ namespace in the future release and won't be considered part of public T-Regx API anymore.

Added in 0.34.1

  • Features
    • Prepared patterns now support changing new line conventions with flag x (EXTENDED mode).

      This code is now valid:

      Pattern::inject("(*CR)#comment@\r@", ['value'], 'x');

      The first placeholder character "@" is considered a part of an extended comment, but the second placeholder @ is assigned value 'value'.

    • All types of PCRE newline conventions are supported: (*LF), (*CR), (*CRLF), (*ANYCRLF), (*ANY), (*NUL).

Added in 0.34.0

  • Breaking changes

    • Moved PlaceholderFigureException from TRegx\CleanRegex\Internal\Prepared\Figure\ to TRegx\CleanRegex\Exception\
  • Features

    • Added support for quantifiers to placeholders in prepared patterns.

      • Added support for quantifiers in Pattern::inject()
      • Added support for quantifiers in Pattern::template()
      • Added support for quantifiers in Pattern::builder()
      • Added support for quantifiers in PcrePattern::inject()
      • Added support for quantifiers in PcrePattern::template()
      • Added support for quantifiers in PcrePattern::builder()

      These constructs are now valid:

      Pattern::inject('Find:@?', ['Value']); // matches "Find:Value" and "Find:"
      Pattern::inject('Find:@*', ['a']); // matches "Find:", "Find:a", "Find:aa"... 
      Pattern::inject('Find:@+', ['a']); // matches "Find:a", "Find:aa"... 
      Pattern::inject('Find:@{3,4}', ['a']); // matches "Find:aaa" and "Find:aaaa" 
      Pattern::inject('Find:@{0}', ['a']); // matches only "Find:" 
    • Added support for empty placeholders in prepared patterns:

      Pattern::inject('Find:@?', ['']); // matches only "Find:"
    • Updated backtrakcing in prepared patterns.

      • Updated backtrakcing in Pattern.pattern().

        Pattern::template('@:Bar')->pattern('Foo:Bar|Foo'); // matches "Foo:Bar" or "Foo:Bar:Bar"
      • Updated backtrakcing in Pattern.mask().

        $template = Pattern::template('@:Bar');
        $template->mask('*', ['*' => 'Foo:Bar|Foo']); // matches "Foo:Bar" or "Foo:Bar:Bar"
  • Other

    • Passing invalid arguments which also don't match the number of arguments in Pattern::inject(), now always prefers \InvalidArgumentException over PlaceholderFigureException.

Added in 0.33.0

  • Breaking changes
    • Removed previously deprecated Pattern.match().group().

    • Removed Pattern.match().asInt(). Use stream().asInt() or Detail.toInt().

    • Pattern.match().first() no longer accepts callable as its argument

    • Pattern.match().findFirst() no longer accepts callable as its argument

    • Stream.first() no longer accepts callable as its argument

    • Stream.findFirst() no longer accepts callable as its argument

    • Refactored Pattern.match() into match() and search().

      Pattern.match() and have virtually the same set of methods, with a slight difference. All of operate on string which is the matched occurrence, and all of Pattern.match() methods now operate on Detail.

  • Features
    • Added, which is similar to Pattern.match(), but its methods only operate on string. From now on, Pattern.match() only operates on Detail.

Added in 0.32.0

  • Breaking changes
    • Removed Pattern.match().tuple().

    • Removed Pattern.match().triple().

      These methods were added in earlier versions of T-Regx, when getting two groups was complicated. However now that the library matured enough, there is plenty of ways to get two groups. Additionally, tuple() implicitly calls first, but it's not obvious from the look of the method.

    • Pattern.match().forEach() callback no longer receives $index as its second argument

    • Pattern.match().group().forEach() callback no longer receives $index as its second argument

    • Pattern.match().stream().forEach() callback no longer receives $key as its second argument

  • Features
    • Added Stream.mapEntries(), similar to, but accepts callable with two arguments ($key, $value), whereas's callable accepts ($value).

Added in 0.31.0

  • Breaking changes
    • Removed Detail.usingDuplicateGroup(). Using J modifier is still allowed, though of little use now.
    • Previously Optional.get() threw exception depending on where the optional came from. Now Optional.get() always throws EmptyOptionalException.
  • Bug fixes
    • Pattern.match().groupBy() now correctly groups by duplicate name with J modifier.

Added in 0.30.0

  • Breaking changes
    • Completely refactored Detail.groups() and Detail.namedGroups()
      • Removed Detail.groups().count()
      • Removed Detail.groups().texts()
      • Removed Detail.groups().offsets()
      • Removed Detail.groups().byteOffsets()
      • Removed Detail.groups().names()
      • Removed Detail.namedGroups().count()
      • Removed Detail.namedGroups().texts()
      • Removed Detail.namedGroups().offsets()
      • Removed Detail.namedGroups().byteOffsets()
      • Removed Detail.namedGroups().names()
  • Features
    • Now Detail.groups() and Detail.namedGroups() return an array of Group.

      • Detail.groups() returns a sequential array of Group (sequential means indexed 0, 1, 2, etc.)
      • Detail.namedGroups() returns an associative array of Group, where array keys are the group names.

Added in 0.29.0

  • Breaking changes
    • Renamed Detail.hasGroup() to Detail.groupExists().
    • Renamed ReplaceDetail.hasGroup() to ReplaceDetail.groupExists().
    • Renamed Pattern.match().hasGroup() to Pattern.match().groupExists().

Added in 0.28.1

  • Bug fixes
    • Calling with invalid integer base on an unmatched group, threw GroupNotMatchedException . Now it throws InvalidArgumentException.
    • Calling with invalid integer base on an unmatched group, threw GroupNotMatchedException . Now it throws InvalidArgumentException.

Added in 0.28.0

  • Breaking changes
    • Removed previously deprecated NotMatched. Every functionallity of NotMatched was also added to Pattern.match() in previous release. This was done to unify the interface of each Optional implementation.
    • Removed previously deprecated
    • Removed IntStream.asInt() which returned itself.
  • Bug fixes
    • Fixed a bug when using usingDuplicateName().group().all() in certain situations resulted in a list of indexed groups, not duplicately-named groups.
    • Corrected exception messages of asInt().limit().
  • Features
    • Updated usages of certain methods, like Detail.groupNames() and others, so that they don't call full matches when it's not necessary, making it usable even when "regular" usage would throw CatastrophicBacktrackingException.
  • Deprecation
    • Deprecated inline-groups Pattern.match().group().
  • Other
    • Previously, just looking up caused full match, potentially ending in catastrophic backtracking. Now, group() doesn't do full match unless it's necessary. Only one call to PREG happens either way, meaning there isn't any performance loss.

Added in 0.27.0

  • Breaking changes
    • Removed Pattern.match().offsets(). Use Pattern.match().map().

    • Removed Pattern.match().group().offsets(). Use Pattern.match().group().map().

    • When using Pattern.split(), previously unmatched separators were represented as an empty string. Now, they're represented as null.

    • Previously Pattern.match().flatMap() reindexed int keys, and preserved string keys, making it similarly ill-behaved as other PHP functions. From now on flatMap() treats any array as a sequential array, that is:

      • It's returned value will be a sequential array (as if array_values() was called on it)
      • Duplicate keys returned from flatMap(callable) will not make values no appear in the result. Only the sequence matters.

      Method Pattern.match().flatMapAssoc() is unchanged, in that case the elements are treated as a dictionary/map - neither string nor int keys are reindexed.

    • Method Optional.orThrow() can no longer be called without an argument. Instead, use Optional.get().

  • Bug fixes
    • Fixed a bug when calling tuple($a,$b) where $a was a non-existent group, and $b was a malformed group, T-Regx threw NonexistentGroupException, instead of InvalidArgumentException. Now it throws InvalidArgumentException.
    • Fixed a bug when calling triple($a,$a,$b) where $a was a non-existent group, and $b was a malformed group, T-Regx threw NonexistentGroupException, instead of InvalidArgumentException. Now it throws InvalidArgumentException.
    • Updated misleading message of GroupNotMatchedException thrown when using groupByCallback().
  • Features
    • Added Pattern.splitStart(), which works similarly to Pattern.split() but accepts a non-negative maxSplits argument, which can be used to limit splits from the start of the subject.
    • Added Pattern.splitEnd(), which works similarly to Pattern.split() but accepts a non-negative maxSplits argument, which can be used to limit splits from the end of the subject.
    • Added method Optional.get(), which returns the optional value or throws an exception (similarly to previous Optional.orThrow(null)).
    • Added methods to Pattern.match():
      • Added Pattern.match().subject(), same as Detail.subject() and NotMatched.subject().
      • Added Pattern.match().hasGroup(), same as Detail.hasGroup() and NotMatched.hasGroup().
      • Added Pattern.match().groupNames(), same as Detail.groupNames() and NotMatched.groupNames().
      • Added Pattern.match().groupsCount(), same as Detail.groupsCount() and NotMatched.groupNames().
  • Deprecation
    • Deprecated NotMatched passed as findFirst().orElse() argument.

Added in 0.26.0

  • Breaking changes
    • match().groupBy(string|int) now returns array of Detail, previously returned array of string.
    • Removed match().groupBy().all(). Use match.groupBy()
    • Removed match().groupBy().offsets(). Use stream().groupByCallback()
    • Removed match().groupBy().byteOffsets(). Use stream().groupByCallback()
    • Removed match().groupBy().map(). Use stream().groupByCallback().map()
    • Removed match().groupBy().flatMap(). Use stream().groupByCallback().flatMap()
    • Removed match().groupBy().flatMapAssoc(). Use stream().groupByCallback().flatMapAssoc()
    • Previously groupBy() simply ignored unmatched groups. Now GroupNotMatchedException is thrown. To control unmatched elements use Group.matched(), stream().filter() or other elements of T-Regx API.

Added in 0.25.0

  • Breaking changes
    • no longer implements Optional

    • Detail.usingDuplicateName().group() no longer implements Optional

    • Removed previously deprecated Use or() instead.

    • Removed previously deprecated

    • Removed previously deprecated

    • Removed previously deprecated Use or() instead.

    • Removed previously deprecated

    • Removed previously deprecated

    • Updated how Stream.distinct() removes elements:

      • Now '1' and true are no longer considered equal
      • Now '' and false are no longer considered equal
      • Now 0 and false are no longer considered equal
      • Now 1 and true are no longer considered equal
      • Now 0 and '0' are no longer considered equal
      • Now null and false are no longer considered equal

      For all intents and purposes, now Stream.distinct() works as-if it used strict-comparison ===.

    • Stream.filter() no longer reindexes stream elements. To reindex them, chain the stream with values(). match().filter() still returns a sequential array with reindexed values.

    • Removed Stream.only(). Use Stream.limit().all() instead.

    • Removed IntStream.only(). Use IntStream.limit().all() instead.

    • Renamed Group.textLength() to Group.length().

    • Renamed Group.textByteLength() to Group.byteLength().

    • Renamed Detail.textLength() to Detail.length().

    • Renamed Detail.textByteLength() to Detail.byteLength().

  • Bug fixes
    • Fixed a bug when using Stream.values().keys().first() didn't always reindex to 0.
    • Fixed a bug when using regular groups in stream()->asInt() was allowed, but usingDuplicateName() groups weren't. Now both kinds of groups are correctly passed into stream()->asInt().
    • Fixed a bug when using regular groups in groupByCallback() was allowed, but usingDuplicateName() groups weren't. Now both kinds of groups are correctly passed into groupByCallback().
  • Features
    • Added which behaves similarly to orReturn() but only accepts a non-nullable string.
    • Added Stream.limit(), which limits elements present in a stream from the end
    • Added Stream.skip(), which limits elements present in a stream from the start
    • Added IntStream.limit(), which limits elements present in a stream from the end
    • Added IntStream.skip(), which limits elements present in a stream from the end
  • Other
    • Previously, using Stream.keys().first() return 0 for sequential arrays, and T-Regx didn't evaluate previous chains, such as map() or flatMap(). As of this release, they will be called for completeness, even though their results won't be used.

Added in 0.24.0

  • Breaking changes
    • Removed previously deprecated Detail.limit(). ReplaceDetail.limit() remains unchanged.
    • Integer methods (toInt(), isInt(), asInt()) accept $base as their optional argument, which defaults to 10.
      • Previously passing null as argument to integer methods was allowed, which also defaulted to 10. Currently, the argument can either be of type int or omitted, but null is no longer allowed as $base.
    • Refactored Pattern::pcre()->of() to PcrePattern::of()
    • Refactored Pattern::pcre()->inject() to PcrePattern::inject()
    • Refactored Pattern::pcre()->builder() to PcrePattern::builder()
    • Refactored Pattern::pcre()->template() to PcrePattern::template()
    • Removed Pattern::pcre()
  • Deprecation
    • Deprecated
    • Deprecated
    • Deprecated
    • Deprecated
    • Deprecated
    • Deprecated
    • Deprecated
    • Deprecated

Added in 0.23.1

  • Bug fixes
    • Normally, pattern errors take precendece over subject errors. That is, NonexistentGroupException should be thrown before CatastrophicBacktrackingException.
      • Fixed a bug when replacing a pattern prone to catastrophic backtracking and unmatched group threw CatastrophicBacktrackingException. Now it throws NonexistentGroupException.

Added in 0.23.0

  • Breaking changes
    • Previously deprecated Optional.orThrow() accepted exception class name as string. Currently, orThrow() accepts an instance of \Throwable.
    • Removed ClassExpectedException, which was thrown when an invalid class name was passed to orThrow().
    • Removed NoSuitableConstructorException, which was thrown when an invalid class was passed to orThrow().
    • Previously T-Regx used whatever encoding was set for mb_internal_encoding() for Detail.offset()/tail() /textLength(). Now, T-Regx always uses UTF-8 regardless of mb_internal_encoding(). For byte manipulation in encoding other than UTF-8 use byteOffset()/byteTail()/byteTextLength().
    • Removed previously deprecated Detail.setUserData()/getUserData()
  • Bug fixes
    • Fixed a bug when using match()->groupByCallback() didn't throw InvalidReturnValueException for invalid group value
  • Deprecation
    • Deprecated Detail.limit()

Added in 0.22.0

  • Breaking changes
    • Changed the namespace of LazyDetail to TRegx\CleanRegex\Replace\By\LazyDetail.
  • Bug fixes
    • Fixed a bug when groups()->texts() with LazyDetail returned '' for an unmatched group on PHP7.1.

      LazyDetail is the implementation of Detail used with replace()->orElseCalling().

    • Fixed a bug when using replace()->by()->group() with a nonexistent group, didn't throw NonexistentGroupException for unmatched subject.

  • Deprecation
    • User-data in Detail will be removed in future release
      • Deprecated Detail.setUserData()
      • Deprecated Detail.getUserData()
  • Other
    • Updated replace()->by()->group(), so that certain scenarios which would normally throw CatastrophicBacktrackingException, are handled in such a way that no catastrophic backtracking happens.

Added in 0.21.0

  • Breaking changes
    • Renamed GroupLimit to GroupMatch.
    • Previously nth()/findNth() threw different exceptions when the subject wasn't matched and when the item was missing. Now they always throw NoSuchNthElementException (regardless of whether the subject was matched or not). Exception messages still remain, to inform you whether there was not enough occurrences or whether the subject wasn't matched.
      • match()->nth() throws NoSuchNthElementException, instead of SubjectNotMatchedException
      • match()->asInt()->nth() throws NoSuchNthElementException, instead of NoSuchStreamElementException
      • match()->stream()->nth() throws NoSuchNthElementException, instead of NoSuchStreamElementException
      • match()->group()->nth() throws NoSuchNthElementException, instead of SubjectNotMatchedException
      • match()->group()->asInt()->nth() throws NoSuchNthElementException, instead of NoSuchStreamElementException
      • match()->group()->stream()->nth() throws NoSuchNthElementException, instead of NoSuchStreamElementException
  • Bug fixes
    • match()->offsets() and match()->group()->offsets() now return offsets as characters. Previously they returned them as bytes.
  • Features
    • Added SubjectNotMatchedException.getSubject()

Added in 0.20.3

  • Performance
    • Improved performance of certain validations, by not parsing PHP errors, when it's not necessary.
  • Features
    • Added pattern()->match()->asInt()->reduce()

Added in 0.20.2

  • Compatibility fixed
    • Removed usage of str_contains() and str_starts_with() from previous version, to maintain support for PHP 7.1.
  • Bug fixes
    • Fixed a bug when using match()->groupBy() for a nonexistent group didn't throw NonexistentGroupException.
  • Features
    • Added Pattern.cut() which splits string into exactly two parts.
  • Performance
    • Refactored internal PCRE parser, to minimise number of instances created.
    • Improved performance of CompositePattern.chainedReplace() by using single call to PCRE.
  • Deprecation
    • Deprecated Detail.substitute().
    • Deprecated pattern()->replace()->focus().

Added in 0.20.1

  • Bug fixes
    • Corrected a bug in prepared patterns, where closing group after in-pattern ((?i)) modifier didn't update the flags properly, resulting in improper parsing of placeholders in Pattern::inject()/Pattern::template().
    • Corrected a bug when using modifier reset (?^) didn't properly unset extended pattern, leading to improper placeholder @ parsing inside comments.
  • Other
    • Previously T-Regx removed duplicate flags before passing them to PCRE. Now flags are passed without modification.

Footnote (15.03.2022):

  • Accidental compatibility breaks:
    • We mistakenly used str_contains() and str_starts_with(). Our tests didn't find it, because we also used php-coveralls/php-coveralls which introduced polyfill for PHP8 methods. It's fixed in 0.20.2.

Added in 0.20.0

  • Breaking changes
    • Removed pattern()->match()->remaining(), as it was a failed idea.
  • Bug fixes
    • Fixed a bug when stream()->map()->asInt() ignored integer-base, assumed base 10.
  • Features
    • Added pattern()->match()->reduce()

Added in 0.19.2

  • Features
    • Added $flags parameter to preg::replace_callback() which was added in PHP 7.4.

Added in 0.19.1

  • Bug fixes
    • Fixed a bug when pattern []] with prepared pattern was parsed incorrectly.
    • Fixed a bug when using character classes (e.g. [:alpha:]) with prepared pattern were parsed incorrectly.

Added in 0.19.0

  • Breaking changes
    • Rename BaseDetail to Structure
  • Features
    • Add second argument Structure as second parameter to replace()->counting()

Added in 0.18.1

  • Features
    • replace()->by()->map() allows for replacing int and numeric strings ("12")

Added in 0.18.0

  • Breaking changes
    • Removed replace()->otherwise(). Use counting() instead.
    • Removed replace()->otherwiseReturning(). Use counting() instead.
    • Removed replace()->otherwiseThrowing(). Use counting() or atLeast() instead.
  • Other

Added in 0.17.0

  • Bug fixes
    • Fixed a bug when returning non-string value from orElseCalling() didn't throw InvalidReturnValueException
  • Breaking changes
    • Renamed FluentMatchPattern to Stream, similar to Java 8 streams
    • Renamed fluent() to stream(), similar to Java 8 streams
    • Renamed NoSuchElementFluentException to NoSuchStreamElementException
  • Features
    • Add

Added in 0.16.0

  • Breaking changes
    • Removed FluentMatchPatternException. In case of asInt(), InvalidIntegerTypeException is thrown instead.

    • Methods asInt() and offsets() return IntStream instead of FluentMatchPattern.

    • Updated the rules when exceptions are thrown from asInt(), offsets() and fluent():

      • Exceptions thrown from IntStream:
        • pattern()->match()->asInt() throws SubjectNotMatchedException
        • pattern()->match()->offsets() throws SubjectNotMatchedException
        • pattern()->match()->group()->asInt() throws SubjectNotMatchedException or GroupNotMatchedException
        • pattern()->match()->group()->offsets() throws SubjectNotMatchedException or GroupNotMatchedException
      • Exception thrown from FluentMatchPattern:
        • pattern()->match()->fluent() throws NoSuchElementFluentException
        • pattern()->match()->asInt()->fluent() throws NoSuchElementFluentException
        • pattern()->match()->offsets()->fluent() throws NoSuchElementFluentException

      Basically, MatchPattern and IntStream throw match-related exceptions (SubjectNotMatchedException or GroupNotMatchedException), whereas FluentMatchPattern throws fluent-related exception: NoSuchElementFluentException.

    • Updated exception messages from asInt(), offsets() and fluent().

    • MatchPatternInterface is no longer part of T-Regx public API.

Added in 0.15.0

  • Breaking changes
    • Renamed Pattern::template() to Pattern::builder()
  • Features
    • Added Pattern::template(), which works similarly to Pattern::builder() but allows only one chain

Added in 0.14.1

  • Bug fixes
    • Fixed a bug when calling filter()->first() called predicate for more than the first item.
  • Deprecation
    • Deprecated Optional.orThrow(). Currently orThrow() accepts the exception class name. In the future it will accept a real \Throwable instance. To preserve current behaviour of orThrow(), use orElse().

Added in 0.14.0

  • Breaking changes
    • Renamed Pattern::compose()->allMatch() to testAll()
    • Renamed Pattern::compose()->anyMatches() to testAny()
    • Renamed Pattern::compose()->chainedRemove() to prune()
  • Features
    • Added Pattern::compose()->failAny(), returning true if any of the patterns didn't match the subject
    • Added Pattern::compose()->failAll(), returning true if all the patterns didn't match the subject

Added in 0.13.8

  • Bug fixes
    • Fixed a bug, where using Pattern::inject('()(?)') failed parsing
    • Fixed a bug, where using unicode in groups failed parsing
    • Fixed a bug, where using pattern in unclosed comment group failed parsing
    • Added workaround for PHP inconsistencies regarding backslash in patterns:
      • PHP reports \c\ as invalid entity, all T-Regx entry points correctly recognize it as valid
      • PHP reports \Q\ as invalid entity, all T-Regx entry points correctly recognize it as valid
      • PHP reports (?#\ as invalid entity, all T-Regx entry points correctly recognize it as valid
      • PHP reports #\ as invalid entity in Xtended mode, all T-Regx entry points correctly recognize it as valid
  • Features
    • Added, which resembles Java 8 optionals.
    • pattern()->match()->asInt()->findFirst()->orElse() receive NotMatched argument
    • pattern()->match()->asInt()->findNth()->orElse() receive NotMatched argument
    • pattern()->match()->offsets()->findFirst()->orElse() receive NotMatched argument
    • pattern()->match()->offsets()->findNth()->orElse() receive NotMatched argument

Added in 0.13.7

  • Breaking changes
    • Pattern::inject() no longer supports alteration. Use Pattern::template()->alteration().
  • Features
    • Added pattern()->match()->forEach() consumer accepts index as a second argument

Added in 0.13.6

  • Bug fixes
    • Fixed a bug, where using match()->filter() didn't throw InvalidReturnValueException.
    • Fixed a bug, where using group()->filter() didn't throw InvalidReturnValueException.
    • Fixed a bug, where Pattern::template()->mask() keywords weren't taken into account, when choosing a delimiter
  • Features
    • Added Pattern::template()->pattern()
  • Others
    • Updated ExplicitDelimiterRequiredException message for Pattern::of()
    • Updated ExplicitDelimiterRequiredException message for Pattern::mask()
    • Updated ExplicitDelimiterRequiredException message for Pattern::template()

Added in 0.13.5

  • Breaking changes
    • Refactored Pattern::pcre() to Pattern::pcre()->of()
    • Refactored Pattern::builder()->pcre()->inject() to Pattern::pcre()->inject()
    • Refactored Pattern::builder()->pcre()->template() to Pattern::pcre()->template()
    • Removed Pattern::builder().
    • Moved ReplaceDetail to TRegx\CleanRegex\Replace\Detail namespace
    • Moved ReplaceGroup to TRegx\CleanRegex\Replace\Detail\Group namespace
  • Features
    • Added Pattern::alteration() which allows building Pattern with just an alteration group.
      • For example Pattern::alteration(['foo', 'bar']) is /(?:foo|bar)/
    • Added Pattern::template()->alteration()
  • Bug fixes
    • Fixed a bug, where passing false as an alteration value didn't throw \InvalidArgumentException.

Added in 0.13.4

  • Features
    • Every method toInt()/isInt() receives a $base optional argument, which defaults to 10:
      • Detail.toInt(), Detail.isInt(),
      • Group.toInt(), Group.isInt(),
      • ReplaceDetail.toInt(), ReplaceDetail.isInt(),
      • pattern()->match()->asInt()
      • pattern()->match()->group()->asInt()
  • Other
    • We added continuous integration runs for PHP on 32-bit architecture, to test 32-bit integers with toInt().

Added in 0.13.3

  • Bug fixes
    • Fixed w bug where using Detail.usingDuplicateName() didn't throw NonexistentGroupException.

Added in 0.13.2

  • Bug fixes
    • Fixed a bug when using match()->asInt()->keys()->first() for malformed integers would not throw NumberFormatException.
    • Fixed a bug when using fluent()->keys()->keys() (double keys()) would silence T-Regx exceptions.
  • Breaking changes
    • Previously remaining() and filter() would leave a resulting array with keys that aren't exactly sequential, giving the impression the iterated collection is not a list. Now it's fixed, so the resulting array is indexed.

Added in 0.13.1

  • Bug fixes
    • Fixed a bug when using match()->fluent()->first() exposed a false-negative in hasGroup() from PHP.
    • Fixed a bug when using match()->group()->fluent()->first() exposed a false-negative in hasGroup() from PHP.
  • Other
    • Internal implementation revamp

Added in 0.13.0

  • Breaking changes
    • pattern()->forArray() is now the same as previous pattern()->forArray()->strict().
    • Removed pattern()->forArray()->strict().
    • Removed Pattern::quote(). Use preg::quote(), which behaves in exactly the same way.
    • Move Pattern::unquote() to preg::unquote(), which behaves in exactly the same way.
    • Removed pattern()->remove()->all(). Use pattern()->prune() instead.
    • Removed pattern()->match()->asArray(). Use Detail.groups() or Detail.namedGroups() instead.
  • Other
    • Using TDD in T-Regx, it wasn't hard to reach 100% coverage quite easily in T-Regx. In order to make the tests even better, we decided that the integration tests won't report any coverage, since it doesn't provide any more information now (it's always 100%). That doesn't mean all the cases are tested tough, so we decided to disable the coverage reports on the Integration tests. Now, the coverage badge will drop, but that doesn't mean we remove any tests. We just mark them as "non-reportable", so that we can use the coverage to actually find more untested cases.

Added in 0.12.0

  • Features
    • We added internal regular expression parser, that's used when creating Prepared patterns. Now in-pattern structures can be properly recognized, eliminating cases of misuse. Most notably [@], \Q@\E, \@, \c@ and others, like comment groups and comments in extended mode.
  • Breaking changes
    • Prepared patterns now use internal regular expression parser, to determine what is a placeholder and what isn't:
      • Previously, [@] would be injected. Now it's treated as "@" character-class.
      • Previously, \Q@\E would be injected. Now it's treated as @ literal.
      • Previously, \c@ would be injected. Now it's \c@ control character.
      • Previously, #@\n would be injected. Now, if x flag is used (globally, or as a subpattern), then it's treated as @ comment.
      • Previously, (?#@) would be injected. Now it's treated as @ comment.
      • Previously, \@ would be treated as @ literal. This remains unchanged.
    • Mask placeholders are no longer represented as & in templates, use @.
    • Refactored Pattern::template()->builder(). Use Pattern::template() now.
    • Removed Pattern::bind(). Use Pattern::inject() or Pattern::template()->literal().
    • Removed Pattern::prepare(). Use Pattern::inject().
    • Removed Pattern::pcre()->bind().
    • Removed Pattern::pcre()->prepare().
    • Removed Pattern::template()->bind().
  • Bug fixes
    • Correct type-error in ValidPattern.isValid() on PHP 8.1.

Added in 0.11.0

  • Features
    • Added Detail.usingDuplicateName().get() #101
    • Added Detail.usingDuplicateName().matched() #101
    • Method Pattern:template()->literal(string) now accepts string argument, allowing for inserting arbitrary strings into the pattern.
    • Added Pattern::builder(), which works similarly to how PatternBuilder::builder() worked.
    • Added Pattern::literal() which creates an instance of a pattern with which matches an arbitrary string exactly, even when x (EXTENDED) flag is used. To add in-pattern structures, like ^ or $, use Pattern::template()->literal().
    • Added Pattern::template()->literal(), which is a shorthand for Pattern::template()->builder()->literal()->build().
    • Added Pattern::template()->mask(), which is a shorthand for Pattern::template()->builder()->mask()->build().
    • Casting PatternInterface to string results in a delimited pattern
    • Add Pcre version helper
  • Breaking changes
    • match()->getIterator() no longer preserves the keys of values (like all())
    • match()->group()->getIterator() no longer preserves the keys of values (like all())
    • Renamed Pattern::format() to Pattern::mask()
    • Renamed Pattern::builder()->format() to Pattern::builder()->mask()
    • Renamed Pattern::template()->format() to Pattern::template()->mask()
    • Refactored Pattern::template()->formatting() to Pattern::template()->builder()->mask()
    • Method literal() now requires argument '&', to escape & in-pattern token
    • Removed PatternBuilder::builder(). Use Pattern::builder()
    • Removed PatternBuilder::compose(). Use Pattern::compose()
    • Renamed FormatMalformedPatternException to MaskMalformedPatternException
    • Removed interface PatternInterface. Now class Pattern is both an instance of a pattern, as well as a static-factory, i.e. Pattern::of()/Pattern::inject().
  • Bug fixes
    • Pattern::template() quoted values incorrectly, when delimiter other than / or % was chosen.

Added in 0.10.2

  • Breaking changes
    • Rename DetailGroup to Group
    • Rename ReplaceDetailGroup to ReplaceGroup
    • Rename BaseDetailGroup to CapturingGroup
  • Features
    • Calling pattern()->replace() without all()/first()/only(), implicitly assumes all()
  • Bug fixes
    • Group name "group\n" used to be considered valid, now it's correctly being treated as invalid.
  • Other
    • ReplaceMatch is now a class, not an interface.
    • When invalid strings, error messages will now also print invisible characters, for example "Foo\n", instead of
    • Update messages and exceptions thrown in edge-cases from group()->fluent() #93

Added in 0.10.1

  • Breaking changes
    • Chainable pattern()->match()->filter() is renamed to remaining().

      pattern()->match()->fluent()->filter() is not being renamed.

    • After filtering MatchPattern with remaining(), consecutive Detail.index() will no longer be reindexed, they will preserve the index() they had had before remaining().

    • match()->fluent()->filter() no longer reindexes values. To reindex, use values().

  • Bug fixes
    • Fixed a bug where fluent()->flatMap()->first() would return the array, instead of the first element
  • Features
    • Add pattern()->match()->filter() which returns only matches allowed by the predicate.
    • Add pattern()->match()->group()->asInt()
  • Other
    • pattern()->match()->fluent()->filter()->first() first calls preg_match(), and if that result doesn't match the predicate, then it calls preg_match_all().

Added in 0.10.0

Added in 0.9.14

  • Breaking changes

    • Rename DetailGroup.replace() to DetailGroup.substitute()
    • Rename match().groupBy().texts() to match().groupBy().all()
    • ReplaceDetail.modifiedOffset() returned values as bytes, now returns them as characters
    • ReplaceDetailGroup.modifiedOffset() returned values as bytes, now returns them as characters
    • Move MalformedPatternException to namespace \TRegx\Exception
    • Move RegexException to namespace \TRegx\Exception
    • MalformedPatternException was a class extending CompilePregException. Now, MalformedPatternException extends only RegexException. New class, PregMalformedPatternException is being thrown everywhere MalformedPatternException used to be thrown. Don't refactor your catch (MalformedPatternException $e), since that's still the recommended handling. (but say get_class() would return PregMalformedPatternException). Complete exception structure is described in "Exceptions".
    • Rename exceptions
      • Rename Utf8OffsetPregException to UnicodeOffsetException
      • Rename SubjectEncodingPregException to SubjectEncodingException
      • Rename CatastrophicBacktrackingPregException to CatastrophicBacktrackingException
      • Rename RecursionLimitPregException to RecursionException
      • Rename JitStackLimitPregException to JitStackLimitException
  • Bug fixes

    • Fix a security bug in Pattern::bind()

    • Using pattern with a trailing backslash (e.g. "(hello)\\") would throw MalformedPatternException with a really weird message, exposing the implementation details. Now the message is Pattern may not end with a trailing backslash.

    • Adapt focus()->withReferences() so it works exactly as preg_replace().

      Previously, using a nonexistent or unmatched group with focus()->withReferences() would throw an exception. But of course, preg_replace() references $1 and \1 simply are ignored by PCRE, being replaced by an empty string. So, as of this version both withReferences() and focus()->withReferences() ignore the unmatched or nonexistent group as well.

    • Fix an error where optionals didn't work properly for match()->offsets()->fluent()

    • Fix an error where ReplaceDetail would return malformed modifiedSubject() for utf-8 replacements

  • Features

    • Add ReplaceDetail.byteModifiedOffset() which returns values as bytes

    • Add ReplaceDetailGroup.byteModifiedOffset() which returns values as bytes

    • Add ReplaceDetailGroup.modifiedSubject()

    • Add pattern formats and pattern templates, a new way of creating pseudo-patterns for user supplied data:

      • Add Pattern::format() #79
      • Add Pattern::template() #79
    • Add Detail.textByteLength() #88

    • Add DetailGroup.textByteLength() #88

    • Add match()->flatMapAssoc() #88

    • Add match()->group()->flatMapAssoc() #88

    • Add match()->fluent()->flatMapAssoc() #88

    • Add match()->groupBy()->flatMapAssoc() #88

      Otherwise identical to flatMap(), but since flatMapAssoc() doesn't use array_merge(), the int keys won't be reindexed - returning an integer key from a flatMapAssoc(). If a given key was already returned previously, the later value will be preserved. It's useful for associative arrays with int keys. For sequential arrays (or arrays with string keys), feel free to use flatMap().

    • Add match()->groupByCallback() (previously only match()->fluent()->groupByCallback() and match()->groupBy()) #80

    • Add match()->nth() (previously only match()->fluent()->nth()) #80

    • Add replace()->counting(), invoking a callback with the number of replacements performed #90

    • Add replace()->exactly(), validating that exactly one/only replacements were performed #90

    • Add replace()->atLeast(), validating that at least one/only replacements were performed #90

    • Add replace()->atMost(), validating that at most one/only replacements were performed #90

    • Add pattern()->prune() which removes every occurrence of a pattern from subject (identical to remove()->all())

  • Other:

    • Replace any usage of \d to [0-9] in the library, since it depends on PHP locale.
    • Added interface PatternStructureException which can be used to catch exceptions for errors solely in pattern structure (recursion, backtracking, jit limit).

Added in 0.9.13

  • Breaking changes

    • None
  • Deprecation

  • Features

    • Add NotReplacedException.getSubject()

    • Add DetailGroup.subject()

    • Add ReplaceDetailGroup.subject()

    • Add pattern()->replace()->focus(group) #82

      It allows the replacement mechanism to focus on a single group, so only the focused capturing group will change; the rest of the whole match will be left as it was.

    • Added proper handling of /J flag #84

      Previously, duplicate patterns added a form of unpredictability - the structure of the group (order, index, name) depended on the group appearance in the pattern, which is fine. However, its value (text, offset) depended on which group was matched (that's what we call strategy 2). That's the consequence of php storing only one named group in the result, since PHP arrays can't hold duplicate keys.

      That's another gotcha trap set by PHP, and we need a reasonable mechanism in T-Regx to handle it.

      Since now, every method (inline groups, group in Match, etc.) predictably depends on the order of the group in the pattern (that's what we call strategy 1), even the value (text, offset), which previously were kind of random.

    • Added Match.usingDuplicateName() method, which allows the user to use the less predictable behaviour (which was the default, previously).

      For safety, groups returned from usingDuplicateName() don't have index() method, since it allows strategy 2, and strategy 2 indexes of groups are sometimes unpredictable. Group returned there extends a different interface, not DetailGroup as usual, but DuplicateNamedGroup - that's an otherwise identical interface, except it doesn't have index() method. Of course, regular group(int|string) groups still have index() method, since they use strategy 1 now.

      There is currently no way to use strategy 2 for inline groups or aggregate group methods, only for Match /Detail details.

  • Other

    • Updated some exceptions' messages format; most notably, indexed groups as formatted as #2, and named groups as 'group'.
  • SafeRegex

Added in 0.9.12

  • Bug fixes
    • Fixed an occasional TypeError (Bug introduced in 0.9.11, fixed in 0.9.12)

      Calling group()->orThrow() on a non-matched group without argument would cause TypeError.

Added in 0.9.11

Added in 0.9.10

  • Breaking changes
    • Renamed BacktrackLimitPregException to CatastrophicBacktrackingPregException.
    • Removed Pattern::prepare().
    • Removed PatternBuilder::prepare().
    • Renamed throwingOtherwise() to otherwiseThrowing().
    • Renamed returningOtherwise() to otherwiseReturning().
  • Features
    • Add pattern()->match()->tuple() method. #76
    • Add pattern()->match()->triple() method. #76

Added in 0.9.9

Added in 0.9.8

  • Features
    • You can now use foreach on match(), instead of forEach():
      foreach (pattern('\d+')->match('') as $match) {}
      and also
      foreach (pattern('\d+')->match('')->asInt() as $digit) {}
      foreach (pattern('\d+')->match('')->all() as $text) {}
    • Added Match.get(string|int), which is a shorthand for|int).text().
    • Restored pattern()->match()->test()/fails() that were removed in version 0.9.2.

Added in 0.9.7

  • Breaking changes
    • pattern()->replace()->orElse/Throw/Return->with() are renamed to otherwise()/throwingOtherwise()/returningOtherwise().
  • Features
    • Added pattern()->match()->asArray()->* which returns results as an array (as if it was returned by preg_match(), but fixed). More below.
  • Bug fixes

When using preg_match() or preg_match_all() with PREG_SET_ORDER, the last groups that are unmatched or matched an empty string are removed by PHP! Missing group, unmatched group and group that matched "" are indistinguishable. Basically, PHP trims any false-y group.

T-Regx fixes it by filling the results:

  • null always means a group is present, but unmatched
  • "" means a matched group, that matched an empty string

Added in 0.9.6

  • Breaking changes
    • pattern()->match()->fluent()->distinct() will no longer re-index elements (will not remove keys).
      • To re-index keys, use distinct()->values().
      • pattern()->match()->distinct() still re-indexes keys.
    • Rename NoFirstElementFluentException to NoSuchElementFluentException
  • Enhancements 🔥
  • Features
    • Added pattern()->match()->fluent()->nth(int) used to get an element based on an ordinal number.
    • Added pattern()->match()->asInt(). More below.

About preg_match() vs preg_match_all():

Previously preg_match() was called only by:

Any other match() method (e.g. map(), forEach(), etc.) used preg_match_all(). From now on, where possible, preg_match() is also used for:

  • fluent()->first()
  • asInt()->first() / asInt()->fluent()->first()
  • group()->first()
  • offsets()->first()
  • group()->offsets()->first()
  • Any method after fluent(), for example fluent()->map()->first()

The same applies to the methods above ending with findFirst().

The change was made because of two reasons:

  • Performance (matching only the first occurrence is faster than all of them)
  • There are cases where the 2nd (or 3rd, n-th) occurrence would have thrown an error (e.g. catastrophic backtracking). Now, such string can be worked with, by calling preg_match() and returning right after first match.

The only exception to this rule is filter()->first(), which still calls preg_match_all().

About asInt() chain

  • New method asInt() can be chained with any match() method:
    • match()->asInt()->all(): int[];
    • match()->asInt()->only(int $limit): int[];
    • match()->asInt()->first(callable $consumer = null): int;
    • match()->asInt()->forEach(callable $consumer): void;
    • match()->asInt()->findFirst(callable $consumer): Optional<int>;
    • match()->asInt()->count(): int; though it doesn't change anything
    • match()->asInt()->iterator(): \Iterator<int>;
    • match()->asInt()->map(callable $mapper): int[];
    • match()->asInt()->flatMap(callable $mapper);
    • match()->asInt()->distinct(): int[];
    • match()->asInt()->filter(callable $predicate): int[];
  • Callbacks passed to first()/map()/flatMap() etc. receive int.
  • asInt()->fluent() is slightly better than fluent()->asInt():
    • fluent()->asInt() creates Match details for each occurrence, which are then cast to int.
    • asInt()->fluent() simply returns matches as int.

Added in 0.9.5

  • Breaking changes
    • Removed:

      • pattern()->match()->fluent()->iterate()
      • pattern()->match()->group()->iterate()
      • pattern()->match()->group()->fluent()->iterate()

      as iterate() was only needed as a substitute for forEach(), pre PHP 7, where methods couldn't be named with keywords.

    • Renamed:

  • Enhancements
    • When no automatic delimiter (/, #, %, ~, etc.) is applicable, character 0x01 is used (provided that it's not used anywhere else in the pattern). #71
  • Features
    • Added match()->group()->findFirst() #22 #70
    • Added alternating groups in prepared patterns 🔥
      • Pattern::bind(), Pattern::inject() and Pattern::prepare() still receive string (as a user input) , but they can also receive string[], which will be treated as a regex alternation group:
        Pattern::bind('Choice: @values', [
            'values' => ['apple?', 'orange', 'pear']
        is similar to
        Pattern::of('Choice: (apple\?|orange|pear)')
        Of course 'apple?' and other values are protected against user-input malformed patterns.
  • Bug fixes
    • Previously, we added uniform quoting of # character on different PHP versions. Well, sorry to say that, we also made a bug doing that, when # was also a delimiter. This bug is fixed now.

Added in 0.9.4

  • Breaking changes
    • Renamed CleanRegexException to PatternException
    • Moved RegexException to \TRegx from \TRegx\CleanRegex\Exception
    • Simplified the namespace of public exceptions:
      • From \TRegx\CleanRegex\Exception\CleanRegex to \TRegx\CleanRegex\Exception
  • Enhancements
  • Features
    • Added match()->groupBy()/match()->filter()->groupBy():

      • match()->groupBy()->texts()
      • match()->groupBy()->map(callable<Match>)
      • match()->groupBy()->flatMap(callable<Match>)
      • match()->groupBy()->offsets()/byteOffsets()

      when groupBy() is preceded by filter(), it will take indexes, limits, matches order and user data into account.

Added in 0.9.3

  • Breaking changes
    • Renamed exceptions:
      • SafeRegexException to PregException
      • CompileSafeRegexException to CompilePregException
      • RuntimeSafeRegexException to RuntimePregException
      • SuspectedReturnSafeRegexException to SuspectedReturnPregException
    • Removed pattern()->match()->iterate() - it was only needed as a substitute for forEach(), pre PHP 7, where methods couldn't be named with keywords.
  • Features
    • Added preg::last_error_msg(), which works like preg::last_error(), but returns a human-readable message, instead of int.
  • Fixing PHP

Added in 0.9.2


  • 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.

Added in 0.9.1

Available in 0.9.0