Skip to content

Commit e1ee097

Browse files
authored
Fix PHP 7.2/7.3 handling of PREG_UNMATCHED_AS_NULL (#16)
1 parent e92ff6e commit e1ee097

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

src/Preg.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static function match(string $pattern, string $subject, ?array &$matches
3030
{
3131
self::checkOffsetCapture($flags, 'matchWithOffsets');
3232

33-
$result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
33+
$result = self::pregMatch($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
3434
if ($result === false) {
3535
throw PcreException::fromFunction('preg_match', $pattern);
3636
}
@@ -69,7 +69,7 @@ public static function matchStrictGroups(string $pattern, string $subject, ?arra
6969
*/
7070
public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
7171
{
72-
$result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
72+
$result = self::pregMatch($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
7373
if ($result === false) {
7474
throw PcreException::fromFunction('preg_match', $pattern);
7575
}
@@ -415,4 +415,40 @@ private static function enforceNonNullMatchAll(string $pattern, array $matches,
415415
/** @var array<int|string, list<string>> */
416416
return $matches;
417417
}
418+
419+
/**
420+
* @param non-empty-string $pattern
421+
* @param array<string|null> $matches Set by method
422+
* @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags
423+
* @return 0|1|false
424+
*
425+
* @param-out array<int|string, string|null> $matches
426+
*/
427+
private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0)
428+
{
429+
if (PHP_VERSION_ID >= 70400) {
430+
return preg_match($pattern, $subject, $matches, $flags, $offset);
431+
}
432+
433+
// On PHP 7.2 and 7.3, PREG_UNMATCHED_AS_NULL only works correctly in preg_match_all
434+
// as preg_match does not set trailing unmatched groups to null
435+
// e.g. preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, PREG_UNMATCHED_AS_NULL); would
436+
// have $match[4] unset instead of null
437+
//
438+
// So we use preg_match_all here as workaround to ensure old-PHP meets the expectations
439+
// set by the library's documentation
440+
$result = preg_match_all($pattern, $subject, $matchesInternal, $flags, $offset);
441+
if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
442+
throw PcreException::fromFunction('preg_match', $pattern);
443+
}
444+
445+
if ($result === 0) {
446+
$matches = [];
447+
} else {
448+
$matches = array_map(function ($m) { return reset($m); }, $matchesInternal);
449+
$result = min($result, 1);
450+
}
451+
452+
return $result;
453+
}
418454
}

tests/BaseTestCase.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ protected function expectPcreWarning(?string $warning = null): void
7171
self::fail('Preg function name is missing');
7272
}
7373

74+
// this is a hack to make the tests work on 7.2/7.3
75+
// @see Preg::pregMatch
76+
if ($this->pregFunction === 'preg_match()' && PHP_VERSION_ID < 70400) {
77+
$this->pregFunction = 'preg_match_all()';
78+
}
79+
7480
$warning = $warning !== null ? $warning : 'No ending matching delimiter \'}\' found';
7581
$message = sprintf('%s: %s', $this->pregFunction, $warning);
7682
$this->doExpectWarning($message);

0 commit comments

Comments
 (0)