123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- <?php
- declare(strict_types=1);
- /*
- * This file is part of PHP CS Fixer.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- * Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace PhpCsFixer;
- /**
- * This class replaces preg_* functions to better handling UTF8 strings,
- * ensuring no matter "u" modifier is present or absent subject will be handled correctly.
- *
- * @author Kuba Werłos <werlos@gmail.com>
- *
- * @internal
- */
- final class Preg
- {
- /**
- * @param array<array-key, mixed> $matches
- * @param int-mask<PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL> $flags
- *
- * @param-out ($flags is PREG_OFFSET_CAPTURE
- * ? array<array-key, array{string, 0|positive-int}|array{'', -1}>
- * : ($flags is PREG_UNMATCHED_AS_NULL
- * ? array<array-key, string|null>
- * : ($flags is int-mask<PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL>&768
- * ? array<array-key, array{string, 0|positive-int}|array{null, -1}>
- * : array<array-key, string>
- * )
- * )
- * ) $matches
- *
- * @throws PregException
- */
- public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
- {
- $result = @preg_match(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset);
- if (false !== $result && PREG_NO_ERROR === preg_last_error()) {
- return 1 === $result;
- }
- $result = @preg_match(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset);
- if (false !== $result && PREG_NO_ERROR === preg_last_error()) {
- return 1 === $result;
- }
- throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern);
- }
- /**
- * @param array<array-key, mixed> $matches
- * @param int-mask<PREG_PATTERN_ORDER, PREG_SET_ORDER, PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL> $flags
- *
- * @param-out ($flags is PREG_PATTERN_ORDER
- * ? array<list<string>>
- * : ($flags is PREG_SET_ORDER
- * ? list<array<string>>
- * : ($flags is int-mask<PREG_PATTERN_ORDER, PREG_OFFSET_CAPTURE>&(256|257)
- * ? array<list<array{string, int}>>
- * : ($flags is int-mask<PREG_SET_ORDER, PREG_OFFSET_CAPTURE>&258
- * ? list<array<array{string, int}>>
- * : ($flags is int-mask<PREG_PATTERN_ORDER, PREG_UNMATCHED_AS_NULL>&(512|513)
- * ? array<list<?string>>
- * : ($flags is int-mask<PREG_SET_ORDER, PREG_UNMATCHED_AS_NULL>&514
- * ? list<array<?string>>
- * : ($flags is int-mask<PREG_SET_ORDER, PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL>&770
- * ? list<array<array{?string, int}>>
- * : ($flags is 0 ? array<list<string>> : array<mixed>)
- * )
- * )
- * )
- * )
- * )
- * )
- * ) $matches
- *
- * @throws PregException
- */
- public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = PREG_PATTERN_ORDER, int $offset = 0): int
- {
- $result = @preg_match_all(self::addUtf8Modifier($pattern), $subject, $matches, $flags, $offset);
- if (false !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- $result = @preg_match_all(self::removeUtf8Modifier($pattern), $subject, $matches, $flags, $offset);
- if (false !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern);
- }
- /**
- * @param array<array-key, string>|string $subject
- *
- * @param-out int $count
- *
- * @throws PregException
- */
- public static function replace(string $pattern, string $replacement, $subject, int $limit = -1, ?int &$count = null): string
- {
- $result = @preg_replace(self::addUtf8Modifier($pattern), $replacement, $subject, $limit, $count);
- if (null !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- $result = @preg_replace(self::removeUtf8Modifier($pattern), $replacement, $subject, $limit, $count);
- if (null !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern);
- }
- /**
- * @param-out int $count
- *
- * @throws PregException
- */
- public static function replaceCallback(string $pattern, callable $callback, string $subject, int $limit = -1, ?int &$count = null): string
- {
- $result = @preg_replace_callback(self::addUtf8Modifier($pattern), $callback, $subject, $limit, $count);
- if (null !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- $result = @preg_replace_callback(self::removeUtf8Modifier($pattern), $callback, $subject, $limit, $count);
- if (null !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern);
- }
- /**
- * @return list<string>
- *
- * @throws PregException
- */
- public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
- {
- $result = @preg_split(self::addUtf8Modifier($pattern), $subject, $limit, $flags);
- if (false !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- $result = @preg_split(self::removeUtf8Modifier($pattern), $subject, $limit, $flags);
- if (false !== $result && PREG_NO_ERROR === preg_last_error()) {
- return $result;
- }
- throw self::newPregException(preg_last_error(), preg_last_error_msg(), __METHOD__, $pattern);
- }
- private static function addUtf8Modifier(string $pattern): string
- {
- return $pattern.'u';
- }
- private static function removeUtf8Modifier(string $pattern): string
- {
- if ('' === $pattern) {
- return '';
- }
- $delimiter = $pattern[0];
- $endDelimiterPosition = strrpos($pattern, $delimiter);
- return substr($pattern, 0, $endDelimiterPosition).str_replace('u', '', substr($pattern, $endDelimiterPosition));
- }
- /**
- * Create the generic PregException message and tell more about such kind of error in the message.
- */
- private static function newPregException(int $error, string $errorMsg, string $method, string $pattern): PregException
- {
- $result = null;
- $errorMessage = null;
- try {
- $result = ExecutorWithoutErrorHandler::execute(static fn () => preg_match($pattern, ''));
- } catch (ExecutorWithoutErrorHandlerException $e) {
- $result = false;
- $errorMessage = $e->getMessage();
- }
- if (false !== $result) {
- return new PregException(sprintf('Unknown error occurred when calling %s: %s.', $method, $errorMsg), $error);
- }
- $code = preg_last_error();
- $message = sprintf(
- '(code: %d) %s',
- $code,
- preg_replace('~preg_[a-z_]+[()]{2}: ~', '', $errorMessage)
- );
- return new PregException(
- sprintf('%s(): Invalid PCRE pattern "%s": %s (version: %s)', $method, $pattern, $message, PCRE_VERSION),
- $code
- );
- }
- }
|