Skip to content

Commit

Permalink
Fix PHP 7.2/7.3 handling of PREG_UNMATCHED_AS_NULL (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seldaek authored Nov 16, 2022
1 parent e92ff6e commit e1ee097
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 2 deletions.
40 changes: 38 additions & 2 deletions src/Preg.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static function match(string $pattern, string $subject, ?array &$matches
{
self::checkOffsetCapture($flags, 'matchWithOffsets');

$result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
$result = self::pregMatch($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
if ($result === false) {
throw PcreException::fromFunction('preg_match', $pattern);
}
Expand Down Expand Up @@ -69,7 +69,7 @@ public static function matchStrictGroups(string $pattern, string $subject, ?arra
*/
public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
{
$result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
$result = self::pregMatch($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
if ($result === false) {
throw PcreException::fromFunction('preg_match', $pattern);
}
Expand Down Expand Up @@ -415,4 +415,40 @@ private static function enforceNonNullMatchAll(string $pattern, array $matches,
/** @var array<int|string, list<string>> */
return $matches;
}

/**
* @param non-empty-string $pattern
* @param array<string|null> $matches Set by method
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags
* @return 0|1|false
*
* @param-out array<int|string, string|null> $matches
*/
private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0)
{
if (PHP_VERSION_ID >= 70400) {
return preg_match($pattern, $subject, $matches, $flags, $offset);
}

// On PHP 7.2 and 7.3, PREG_UNMATCHED_AS_NULL only works correctly in preg_match_all
// as preg_match does not set trailing unmatched groups to null
// e.g. preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, PREG_UNMATCHED_AS_NULL); would
// have $match[4] unset instead of null
//
// So we use preg_match_all here as workaround to ensure old-PHP meets the expectations
// set by the library's documentation
$result = preg_match_all($pattern, $subject, $matchesInternal, $flags, $offset);
if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
throw PcreException::fromFunction('preg_match', $pattern);
}

if ($result === 0) {
$matches = [];
} else {
$matches = array_map(function ($m) { return reset($m); }, $matchesInternal);
$result = min($result, 1);
}

return $result;
}
}
6 changes: 6 additions & 0 deletions tests/BaseTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ protected function expectPcreWarning(?string $warning = null): void
self::fail('Preg function name is missing');
}

// this is a hack to make the tests work on 7.2/7.3
// @see Preg::pregMatch
if ($this->pregFunction === 'preg_match()' && PHP_VERSION_ID < 70400) {
$this->pregFunction = 'preg_match_all()';
}

$warning = $warning !== null ? $warning : 'No ending matching delimiter \'}\' found';
$message = sprintf('%s: %s', $this->pregFunction, $warning);
$this->doExpectWarning($message);
Expand Down

0 comments on commit e1ee097

Please sign in to comment.