From cdfce77daf6c29ed479c6297b1c210511eaed79f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 19 May 2021 15:18:37 +0200 Subject: [PATCH 01/68] Bump Symfony 6 to PHP 8 --- composer.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index bfb03cd..fe03135 100644 --- a/composer.json +++ b/composer.json @@ -16,12 +16,11 @@ } ], "require": { - "php": ">=7.2.5", + "php": ">=8.0.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "symfony/error-handler": "^4.4|^5.0", From d71023c4ac9411acd1fcf323bad3820b42582ed2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 May 2021 14:59:02 +0200 Subject: [PATCH 02/68] Bump symfony/* deps to ^5.4|^6.0 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c0c1206..aca9e09 100644 --- a/composer.json +++ b/composer.json @@ -23,10 +23,10 @@ "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/var-exporter": "^5.4|^6.0" }, "autoload": { "psr-4": { "Symfony\\Component\\String\\": "" }, From 8732b6f3d961c38bbb407deab90bee201a5563f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 19 May 2021 20:52:47 +0200 Subject: [PATCH 03/68] Remove constraint for PHP < 8 --- AbstractUnicodeString.php | 6 +----- LazyString.php | 5 ----- UnicodeString.php | 6 ------ 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index ed186cc..00e4898 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -191,7 +191,7 @@ public function folded(bool $compat = true): parent { $str = clone $this; - if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) { + if (!$compat || !\defined('Normalizer::NFKC_CF')) { $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8'); } else { @@ -427,10 +427,6 @@ public function upper(): parent $str = clone $this; $str->string = mb_strtoupper($str->string, 'UTF-8'); - if (\PHP_VERSION_ID < 70300) { - $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string); - } - return $str; } diff --git a/LazyString.php b/LazyString.php index b3801db..f2cca42 100644 --- a/LazyString.php +++ b/LazyString.php @@ -111,11 +111,6 @@ public function __toString() $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); } - if (\PHP_VERSION_ID < 70400) { - // leverage the ErrorHandler component with graceful fallback when it's not available - return trigger_error($e, \E_USER_ERROR); - } - throw $e; } } diff --git a/UnicodeString.php b/UnicodeString.php index 9b906c6..b3b5527 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -268,9 +268,6 @@ public function slice(int $start = 0, int $length = null): AbstractString { $str = clone $this; - if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { - $start = 0; - } $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647); return $str; @@ -280,9 +277,6 @@ public function splice(string $replacement, int $start = 0, int $length = null): { $str = clone $this; - if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { - $start = 0; - } $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); From b995c211729a96e0aec4d5e67219a0d38a6f1793 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 28 May 2021 09:40:39 +0200 Subject: [PATCH 04/68] [String] add types to all arguments --- AbstractString.php | 56 +++++++++++++++++++++++++-------------- AbstractUnicodeString.php | 2 +- ByteString.php | 38 +++++++++++--------------- CodePointString.php | 30 +++++++-------------- LazyString.php | 22 +++++---------- Slugger/AsciiSlugger.php | 9 +------ UnicodeString.php | 32 ++++++++-------------- 7 files changed, 81 insertions(+), 108 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index d3d95d4..62a4f89 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -95,12 +95,16 @@ public static function wrap(array $values): array * * @return static */ - public function after($needle, bool $includeNeedle = false, int $offset = 0): self + public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = \PHP_INT_MAX; - foreach ((array) $needle as $n) { + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { $n = (string) $n; $j = $this->indexOf($n, $offset); @@ -126,12 +130,16 @@ public function after($needle, bool $includeNeedle = false, int $offset = 0): se * * @return static */ - public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self + public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = null; - foreach ((array) $needle as $n) { + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { $n = (string) $n; $j = $this->indexOfLast($n, $offset); @@ -162,12 +170,16 @@ abstract public function append(string ...$suffix): self; * * @return static */ - public function before($needle, bool $includeNeedle = false, int $offset = 0): self + public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = \PHP_INT_MAX; - foreach ((array) $needle as $n) { + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { $n = (string) $n; $j = $this->indexOf($n, $offset); @@ -193,12 +205,16 @@ public function before($needle, bool $includeNeedle = false, int $offset = 0): s * * @return static */ - public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self + public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self { $str = clone $this; $i = null; - foreach ((array) $needle as $n) { + if (\is_string($needle)) { + $needle = [$needle]; + } + + foreach ($needle as $n) { $n = (string) $n; $j = $this->indexOfLast($n, $offset); @@ -253,7 +269,7 @@ public function collapseWhitespace(): self /** * @param string|string[] $needle */ - public function containsAny($needle): bool + public function containsAny(string|iterable $needle): bool { return null !== $this->indexOf($needle); } @@ -261,9 +277,9 @@ public function containsAny($needle): bool /** * @param string|string[] $suffix */ - public function endsWith($suffix): bool + public function endsWith(string|iterable $suffix): bool { - if (!\is_array($suffix) && !$suffix instanceof \Traversable) { + if (\is_string($suffix)) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } @@ -316,9 +332,9 @@ public function ensureStart(string $prefix): self /** * @param string|string[] $string */ - public function equalsTo($string): bool + public function equalsTo(string|iterable $string): bool { - if (!\is_array($string) && !$string instanceof \Traversable) { + if (\is_string($string)) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } @@ -350,9 +366,9 @@ public function ignoreCase(): self /** * @param string|string[] $needle */ - public function indexOf($needle, int $offset = 0): ?int + public function indexOf(string|iterable $needle, int $offset = 0): ?int { - if (!\is_array($needle) && !$needle instanceof \Traversable) { + if (\is_string($needle)) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } @@ -372,9 +388,9 @@ public function indexOf($needle, int $offset = 0): ?int /** * @param string|string[] $needle */ - public function indexOfLast($needle, int $offset = 0): ?int + public function indexOfLast(string|iterable $needle, int $offset = 0): ?int { - if (!\is_array($needle) && !$needle instanceof \Traversable) { + if (\is_string($needle)) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } @@ -467,7 +483,7 @@ abstract public function replace(string $from, string $to): self; * * @return static */ - abstract public function replaceMatches(string $fromRegexp, $to): self; + abstract public function replaceMatches(string $fromRegexp, string|callable $to): self; /** * @return static @@ -540,9 +556,9 @@ public function split(string $delimiter, int $limit = null, int $flags = null): /** * @param string|string[] $prefix */ - public function startsWith($prefix): bool + public function startsWith(string|iterable $prefix): bool { - if (!\is_array($prefix) && !$prefix instanceof \Traversable) { + if (\is_string($prefix)) { throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 00e4898..e24fa3c 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -303,7 +303,7 @@ public function padStart(int $length, string $padStr = ' '): parent return $this->pad($length, $pad, \STR_PAD_LEFT); } - public function replaceMatches(string $fromRegexp, $to): parent + public function replaceMatches(string $fromRegexp, string|callable $to): parent { if ($this->ignoreCase) { $fromRegexp .= 'i'; diff --git a/ByteString.php b/ByteString.php index bbf8614..63183bd 100644 --- a/ByteString.php +++ b/ByteString.php @@ -129,27 +129,23 @@ public function chunk(int $length = 1): array return $chunks; } - public function endsWith($suffix): bool + public function endsWith(string|iterable|AbstractString $suffix): bool { - if ($suffix instanceof parent) { + if ($suffix instanceof AbstractString) { $suffix = $suffix->string; - } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + } elseif (!\is_string($suffix)) { return parent::endsWith($suffix); - } else { - $suffix = (string) $suffix; } return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); } - public function equalsTo($string): bool + public function equalsTo(string|iterable|AbstractString $string): bool { - if ($string instanceof parent) { + if ($string instanceof AbstractString) { $string = $string->string; - } elseif (\is_array($string) || $string instanceof \Traversable) { + } elseif (!\is_string($string)) { return parent::equalsTo($string); - } else { - $string = (string) $string; } if ('' !== $string && $this->ignoreCase) { @@ -167,14 +163,12 @@ public function folded(): parent return $str; } - public function indexOf($needle, int $offset = 0): ?int + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int { - if ($needle instanceof parent) { + if ($needle instanceof AbstractString) { $needle = $needle->string; - } elseif (\is_array($needle) || $needle instanceof \Traversable) { + } elseif (!\is_string($needle)) { return parent::indexOf($needle, $offset); - } else { - $needle = (string) $needle; } if ('' === $needle) { @@ -186,14 +180,12 @@ public function indexOf($needle, int $offset = 0): ?int return false === $i ? null : $i; } - public function indexOfLast($needle, int $offset = 0): ?int + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int { - if ($needle instanceof parent) { + if ($needle instanceof AbstractString) { $needle = $needle->string; - } elseif (\is_array($needle) || $needle instanceof \Traversable) { + } elseif (!\is_string($needle)) { return parent::indexOfLast($needle, $offset); - } else { - $needle = (string) $needle; } if ('' === $needle) { @@ -305,7 +297,7 @@ public function replace(string $from, string $to): parent return $str; } - public function replaceMatches(string $fromRegexp, $to): parent + public function replaceMatches(string $fromRegexp, string|callable $to): parent { if ($this->ignoreCase) { $fromRegexp .= 'i'; @@ -404,9 +396,9 @@ public function split(string $delimiter, int $limit = null, int $flags = null): return $chunks; } - public function startsWith($prefix): bool + public function startsWith(string|iterable|AbstractString $prefix): bool { - if ($prefix instanceof parent) { + if ($prefix instanceof AbstractString) { $prefix = $prefix->string; } elseif (!\is_string($prefix)) { return parent::startsWith($prefix); diff --git a/CodePointString.php b/CodePointString.php index 8ab9209..de52a82 100644 --- a/CodePointString.php +++ b/CodePointString.php @@ -80,14 +80,12 @@ public function codePointsAt(int $offset): array return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')]; } - public function endsWith($suffix): bool + public function endsWith(string|iterable|AbstractString $suffix): bool { if ($suffix instanceof AbstractString) { $suffix = $suffix->string; - } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + } elseif (!\is_string($suffix)) { return parent::endsWith($suffix); - } else { - $suffix = (string) $suffix; } if ('' === $suffix || !preg_match('//u', $suffix)) { @@ -101,14 +99,12 @@ public function endsWith($suffix): bool return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix)); } - public function equalsTo($string): bool + public function equalsTo(string|iterable|AbstractString $string): bool { if ($string instanceof AbstractString) { $string = $string->string; - } elseif (\is_array($string) || $string instanceof \Traversable) { + } elseif (!\is_string($string)) { return parent::equalsTo($string); - } else { - $string = (string) $string; } if ('' !== $string && $this->ignoreCase) { @@ -118,14 +114,12 @@ public function equalsTo($string): bool return $string === $this->string; } - public function indexOf($needle, int $offset = 0): ?int + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; - } elseif (\is_array($needle) || $needle instanceof \Traversable) { + } elseif (!\is_string($needle)) { return parent::indexOf($needle, $offset); - } else { - $needle = (string) $needle; } if ('' === $needle) { @@ -137,14 +131,12 @@ public function indexOf($needle, int $offset = 0): ?int return false === $i ? null : $i; } - public function indexOfLast($needle, int $offset = 0): ?int + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; - } elseif (\is_array($needle) || $needle instanceof \Traversable) { + } elseif (!\is_string($needle)) { return parent::indexOfLast($needle, $offset); - } else { - $needle = (string) $needle; } if ('' === $needle) { @@ -247,14 +239,12 @@ public function split(string $delimiter, int $limit = null, int $flags = null): return $chunks; } - public function startsWith($prefix): bool + public function startsWith(string|iterable|AbstractString $prefix): bool { if ($prefix instanceof AbstractString) { $prefix = $prefix->string; - } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + } elseif (!\is_string($prefix)) { return parent::startsWith($prefix); - } else { - $prefix = (string) $prefix; } if ('' === $prefix || !preg_match('//u', $prefix)) { diff --git a/LazyString.php b/LazyString.php index f2cca42..7f32c47 100644 --- a/LazyString.php +++ b/LazyString.php @@ -25,10 +25,10 @@ class LazyString implements \Stringable, \JsonSerializable * * @return static */ - public static function fromCallable($callback, ...$arguments): self + public static function fromCallable(callable|array $callback, mixed ...$arguments): self { - if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback))); + if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) instanceof \Closure || 2 < \count($callback))) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']')); } $lazyString = new static(); @@ -50,16 +50,10 @@ public static function fromCallable($callback, ...$arguments): self } /** - * @param string|int|float|bool|\Stringable $value - * * @return static */ - public static function fromStringable($value): self + public static function fromStringable(string|int|float|bool|\Stringable $value): self { - if (!self::isStringable($value)) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value))); - } - if (\is_object($value)) { return static::fromCallable([$value, '__toString']); } @@ -73,19 +67,17 @@ public static function fromStringable($value): self /** * Tells whether the provided value can be cast to string. */ - final public static function isStringable($value): bool + final public static function isStringable(mixed $value): bool { - return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : is_scalar($value)); + return \is_string($value) || $value instanceof \Stringable || is_scalar($value); } /** * Casts scalars and stringable objects to strings. * - * @param object|string|int|float|bool $value - * * @throws \TypeError When the provided value is not stringable */ - final public static function resolve($value): string + final public static function resolve(\Stringable|string|int|float|bool $value): string { return $value; } diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 5aecfeb..a4bcd81 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -66,15 +66,8 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface */ private $transliterators = []; - /** - * @param array|\Closure|null $symbolsMap - */ - public function __construct(string $defaultLocale = null, $symbolsMap = null) + public function __construct(string $defaultLocale = null, array|\Closure|null $symbolsMap = null) { - if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) { - throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap))); - } - $this->defaultLocale = $defaultLocale; $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } diff --git a/UnicodeString.php b/UnicodeString.php index b3b5527..b3d4f50 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -82,14 +82,12 @@ public function chunk(int $length = 1): array return $chunks; } - public function endsWith($suffix): bool + public function endsWith(string|iterable|AbstractString $suffix): bool { if ($suffix instanceof AbstractString) { $suffix = $suffix->string; - } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + } elseif (!\is_string($suffix)) { return parent::endsWith($suffix); - } else { - $suffix = (string) $suffix; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; @@ -106,14 +104,12 @@ public function endsWith($suffix): bool return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); } - public function equalsTo($string): bool + public function equalsTo(string|iterable|AbstractString $string): bool { if ($string instanceof AbstractString) { $string = $string->string; - } elseif (\is_array($string) || $string instanceof \Traversable) { + } elseif (!\is_string($string)) { return parent::equalsTo($string); - } else { - $string = (string) $string; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; @@ -126,14 +122,12 @@ public function equalsTo($string): bool return $string === $this->string; } - public function indexOf($needle, int $offset = 0): ?int + public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; - } elseif (\is_array($needle) || $needle instanceof \Traversable) { + } elseif (!\is_string($needle)) { return parent::indexOf($needle, $offset); - } else { - $needle = (string) $needle; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; @@ -152,14 +146,12 @@ public function indexOf($needle, int $offset = 0): ?int return false === $i ? null : $i; } - public function indexOfLast($needle, int $offset = 0): ?int + public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int { if ($needle instanceof AbstractString) { $needle = $needle->string; - } elseif (\is_array($needle) || $needle instanceof \Traversable) { + } elseif (!\is_string($needle)) { return parent::indexOfLast($needle, $offset); - } else { - $needle = (string) $needle; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; @@ -256,7 +248,7 @@ public function replace(string $from, string $to): AbstractString return $str; } - public function replaceMatches(string $fromRegexp, $to): AbstractString + public function replaceMatches(string $fromRegexp, string|callable $to): AbstractString { $str = parent::replaceMatches($fromRegexp, $to); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); @@ -327,14 +319,12 @@ public function split(string $delimiter, int $limit = null, int $flags = null): return $chunks; } - public function startsWith($prefix): bool + public function startsWith(string|iterable|AbstractString $prefix): bool { if ($prefix instanceof AbstractString) { $prefix = $prefix->string; - } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + } elseif (!\is_string($prefix)) { return parent::startsWith($prefix); - } else { - $prefix = (string) $prefix; } $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; From 6af24b577b18038efe420359394f6c3e01c8947d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 28 May 2021 17:52:26 +0200 Subject: [PATCH 05/68] Add return type to __toString() --- LazyString.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/LazyString.php b/LazyString.php index 7f32c47..456ec87 100644 --- a/LazyString.php +++ b/LazyString.php @@ -82,10 +82,7 @@ final public static function resolve(\Stringable|string|int|float|bool $value): return $value; } - /** - * @return string - */ - public function __toString() + public function __toString(): string { if (\is_string($this->value)) { return $this->value; From 70d70a34d971e1851250de60566c29f730b4f141 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Jun 2021 18:09:43 +0200 Subject: [PATCH 06/68] Update phpunit.xml.dist files for phpunit >= 9.3 --- phpunit.xml.dist | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 619183a..32741bd 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - - + + ./ - - ./Tests - ./vendor - - - + + + ./Tests + ./vendor + + From 6ef6e18ff89775fa844aedf0c342a652cd8e6fa9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 6 Jun 2021 15:54:44 +0200 Subject: [PATCH 07/68] [Templating][String][Stopwatch] add union types --- Slugger/AsciiSlugger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index a4bcd81..c62dcd9 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -75,7 +75,7 @@ public function __construct(string $defaultLocale = null, array|\Closure|null $s /** * {@inheritdoc} */ - public function setLocale($locale) + public function setLocale(string $locale) { $this->defaultLocale = $locale; } From ec60b766bd3d819dd69eff9cce776995a1612fcb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Jun 2021 12:09:37 +0200 Subject: [PATCH 08/68] Bump Contracts to 2.5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aca9e09..b3be5e0 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^1.1|^2", + "symfony/translation-contracts": "^2", "symfony/var-exporter": "^5.4|^6.0" }, "autoload": { From e025988d94acc4d9b751b5d791c7453d018f35d5 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 14 Jun 2021 10:52:22 +0200 Subject: [PATCH 09/68] conflict with Translation contracts 1.x --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index b3be5e0..05d2897 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,9 @@ "symfony/translation-contracts": "^2", "symfony/var-exporter": "^5.4|^6.0" }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, "autoload": { "psr-4": { "Symfony\\Component\\String\\": "" }, "files": [ "Resources/functions.php" ], From 6b16770dd781dd9c143780aa24c77cec8808654f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 13 Jul 2021 13:47:05 +0200 Subject: [PATCH 10/68] [Contracts] add return types and bump to v3 --- Slugger/AsciiSlugger.php | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 82ce1dd..1c75aa8 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -83,7 +83,7 @@ public function setLocale(string $locale) /** * {@inheritdoc} */ - public function getLocale() + public function getLocale(): string { return $this->defaultLocale; } diff --git a/composer.json b/composer.json index 05d2897..187323f 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require-dev": { "symfony/error-handler": "^5.4|^6.0", "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2", + "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, "conflict": { From ef88010d913fc942d886635f4076ded06c31bcd5 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 15 Jul 2021 02:52:23 +0200 Subject: [PATCH 11/68] [String] Add types to private properties Signed-off-by: Alexander M. Turek --- LazyString.php | 2 +- Resources/WcswidthDataGenerator.php | 6 +++--- Slugger/AsciiSlugger.php | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LazyString.php b/LazyString.php index 456ec87..ba16888 100644 --- a/LazyString.php +++ b/LazyString.php @@ -18,7 +18,7 @@ */ class LazyString implements \Stringable, \JsonSerializable { - private $value; + private \Closure|string $value; /** * @param callable|array $callback A callable or a [Closure, method] lazy-callable diff --git a/Resources/WcswidthDataGenerator.php b/Resources/WcswidthDataGenerator.php index 69d32e2..10e68d9 100644 --- a/Resources/WcswidthDataGenerator.php +++ b/Resources/WcswidthDataGenerator.php @@ -14,15 +14,15 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\String\Exception\RuntimeException; use Symfony\Component\VarExporter\VarExporter; +use Symfony\Contracts\HttpClient\HttpClientInterface; /** * @internal */ final class WcswidthDataGenerator { - private $outDir; - - private $client; + private string $outDir; + private HttpClientInterface $client; public function __construct(string $outDir) { diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 82ce1dd..76d0894 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -54,8 +54,8 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface 'zh' => 'Han-Latin', ]; - private $defaultLocale; - private $symbolsMap = [ + private ?string $defaultLocale; + private \Closure|array $symbolsMap = [ 'en' => ['@' => 'at', '&' => 'and'], ]; @@ -64,7 +64,7 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface * * @var \Transliterator[] */ - private $transliterators = []; + private array $transliterators = []; public function __construct(string $defaultLocale = null, array|\Closure $symbolsMap = null) { From 1057386877494a8f5db303c33f6f00db85dc3c19 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Jul 2021 12:04:28 +0200 Subject: [PATCH 12/68] Narrow existing return types on private/internal/final/test methods --- AbstractUnicodeString.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 73334df..ecb5348 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -459,10 +459,7 @@ public function width(bool $ignoreAnsiDecoration = true): int return $width; } - /** - * @return static - */ - private function pad(int $len, self $pad, int $type): parent + private function pad(int $len, self $pad, int $type): static { $sLen = $this->length(); From 088eb6f6ec7ac822f7cf688f64975532dd51bdc8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Aug 2021 16:46:41 +0200 Subject: [PATCH 13/68] Add return types - batch 1/n --- AbstractString.php | 62 +++++++++++++++++++-------------------- AbstractUnicodeString.php | 34 ++++++++++----------- ByteString.php | 40 ++++++++++++------------- CodePointString.php | 10 +++---- LazyString.php | 4 +-- UnicodeString.php | 16 +++++----- 6 files changed, 83 insertions(+), 83 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index 62a4f89..71a981e 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -95,7 +95,7 @@ public static function wrap(array $values): array * * @return static */ - public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self + public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { $str = clone $this; $i = \PHP_INT_MAX; @@ -130,7 +130,7 @@ public function after(string|iterable $needle, bool $includeNeedle = false, int * * @return static */ - public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self + public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { $str = clone $this; $i = null; @@ -163,14 +163,14 @@ public function afterLast(string|iterable $needle, bool $includeNeedle = false, /** * @return static */ - abstract public function append(string ...$suffix): self; + abstract public function append(string ...$suffix): static; /** * @param string|string[] $needle * * @return static */ - public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self + public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { $str = clone $this; $i = \PHP_INT_MAX; @@ -205,7 +205,7 @@ public function before(string|iterable $needle, bool $includeNeedle = false, int * * @return static */ - public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): self + public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { $str = clone $this; $i = null; @@ -248,7 +248,7 @@ public function bytesAt(int $offset): array /** * @return static */ - abstract public function camel(): self; + abstract public function camel(): static; /** * @return static[] @@ -258,7 +258,7 @@ abstract public function chunk(int $length = 1): array; /** * @return static */ - public function collapseWhitespace(): self + public function collapseWhitespace(): static { $str = clone $this; $str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string)); @@ -295,7 +295,7 @@ public function endsWith(string|iterable $suffix): bool /** * @return static */ - public function ensureEnd(string $suffix): self + public function ensureEnd(string $suffix): static { if (!$this->endsWith($suffix)) { return $this->append($suffix); @@ -310,7 +310,7 @@ public function ensureEnd(string $suffix): self /** * @return static */ - public function ensureStart(string $prefix): self + public function ensureStart(string $prefix): static { $prefix = new static($prefix); @@ -350,12 +350,12 @@ public function equalsTo(string|iterable $string): bool /** * @return static */ - abstract public function folded(): self; + abstract public function folded(): static; /** * @return static */ - public function ignoreCase(): self + public function ignoreCase(): static { $str = clone $this; $str->ignoreCase = true; @@ -415,7 +415,7 @@ public function isEmpty(): bool /** * @return static */ - abstract public function join(array $strings, string $lastGlue = null): self; + abstract public function join(array $strings, string $lastGlue = null): static; public function jsonSerialize(): string { @@ -427,7 +427,7 @@ abstract public function length(): int; /** * @return static */ - abstract public function lower(): self; + abstract public function lower(): static; /** * Matches the string using a regular expression. @@ -441,27 +441,27 @@ abstract public function match(string $regexp, int $flags = 0, int $offset = 0): /** * @return static */ - abstract public function padBoth(int $length, string $padStr = ' '): self; + abstract public function padBoth(int $length, string $padStr = ' '): static; /** * @return static */ - abstract public function padEnd(int $length, string $padStr = ' '): self; + abstract public function padEnd(int $length, string $padStr = ' '): static; /** * @return static */ - abstract public function padStart(int $length, string $padStr = ' '): self; + abstract public function padStart(int $length, string $padStr = ' '): static; /** * @return static */ - abstract public function prepend(string ...$prefix): self; + abstract public function prepend(string ...$prefix): static; /** * @return static */ - public function repeat(int $multiplier): self + public function repeat(int $multiplier): static { if (0 > $multiplier) { throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); @@ -476,34 +476,34 @@ public function repeat(int $multiplier): self /** * @return static */ - abstract public function replace(string $from, string $to): self; + abstract public function replace(string $from, string $to): static; /** * @param string|callable $to * * @return static */ - abstract public function replaceMatches(string $fromRegexp, string|callable $to): self; + abstract public function replaceMatches(string $fromRegexp, string|callable $to): static; /** * @return static */ - abstract public function reverse(): self; + abstract public function reverse(): static; /** * @return static */ - abstract public function slice(int $start = 0, int $length = null): self; + abstract public function slice(int $start = 0, int $length = null): static; /** * @return static */ - abstract public function snake(): self; + abstract public function snake(): static; /** * @return static */ - abstract public function splice(string $replacement, int $start = 0, int $length = null): self; + abstract public function splice(string $replacement, int $start = 0, int $length = null): static; /** * @return static[] @@ -574,7 +574,7 @@ public function startsWith(string|iterable $prefix): bool /** * @return static */ - abstract public function title(bool $allWords = false): self; + abstract public function title(bool $allWords = false): static; public function toByteString(string $toEncoding = null): ByteString { @@ -625,22 +625,22 @@ public function toUnicodeString(): UnicodeString /** * @return static */ - abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; /** * @return static */ - abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; /** * @return static */ - abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; /** * @return static */ - public function truncate(int $length, string $ellipsis = '', bool $cut = true): self + public function truncate(int $length, string $ellipsis = '', bool $cut = true): static { $stringLength = $this->length(); @@ -670,7 +670,7 @@ public function truncate(int $length, string $ellipsis = '', bool $cut = true): /** * @return static */ - abstract public function upper(): self; + abstract public function upper(): static; /** * Returns the printable length on a terminal. @@ -680,7 +680,7 @@ abstract public function width(bool $ignoreAnsiDecoration = true): int; /** * @return static */ - public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self + public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static { $lines = '' !== $break ? $this->split($break) : [clone $this]; $chars = []; diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index ecb5348..dc85c67 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -53,7 +53,7 @@ abstract class AbstractUnicodeString extends AbstractString /** * @return static */ - public static function fromCodePoints(int ...$codes): self + public static function fromCodePoints(int ...$codes): static { $string = ''; @@ -157,7 +157,7 @@ public function ascii(array $rules = []): self return $str; } - public function camel(): parent + public function camel(): static { $str = clone $this; $str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) { @@ -187,7 +187,7 @@ public function codePointsAt(int $offset): array return $codePoints; } - public function folded(bool $compat = true): parent + public function folded(bool $compat = true): static { $str = clone $this; @@ -201,7 +201,7 @@ public function folded(bool $compat = true): parent return $str; } - public function join(array $strings, string $lastGlue = null): parent + public function join(array $strings, string $lastGlue = null): static { $str = clone $this; @@ -215,7 +215,7 @@ public function join(array $strings, string $lastGlue = null): parent return $str; } - public function lower(): parent + public function lower(): static { $str = clone $this; $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8'); @@ -255,7 +255,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array /** * @return static */ - public function normalize(int $form = self::NFC): self + public function normalize(int $form = self::NFC): static { if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { throw new InvalidArgumentException('Unsupported normalization form.'); @@ -267,7 +267,7 @@ public function normalize(int $form = self::NFC): self return $str; } - public function padBoth(int $length, string $padStr = ' '): parent + public function padBoth(int $length, string $padStr = ' '): static { if ('' === $padStr || !preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); @@ -279,7 +279,7 @@ public function padBoth(int $length, string $padStr = ' '): parent return $this->pad($length, $pad, \STR_PAD_BOTH); } - public function padEnd(int $length, string $padStr = ' '): parent + public function padEnd(int $length, string $padStr = ' '): static { if ('' === $padStr || !preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); @@ -291,7 +291,7 @@ public function padEnd(int $length, string $padStr = ' '): parent return $this->pad($length, $pad, \STR_PAD_RIGHT); } - public function padStart(int $length, string $padStr = ' '): parent + public function padStart(int $length, string $padStr = ' '): static { if ('' === $padStr || !preg_match('//u', $padStr)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); @@ -303,7 +303,7 @@ public function padStart(int $length, string $padStr = ' '): parent return $this->pad($length, $pad, \STR_PAD_LEFT); } - public function replaceMatches(string $fromRegexp, string|callable $to): parent + public function replaceMatches(string $fromRegexp, string|callable $to): static { if ($this->ignoreCase) { $fromRegexp .= 'i'; @@ -354,7 +354,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): parent return $str; } - public function reverse(): parent + public function reverse(): static { $str = clone $this; $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); @@ -362,7 +362,7 @@ public function reverse(): parent return $str; } - public function snake(): parent + public function snake(): static { $str = $this->camel()->title(); $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8'); @@ -370,7 +370,7 @@ public function snake(): parent return $str; } - public function title(bool $allWords = false): parent + public function title(bool $allWords = false): static { $str = clone $this; @@ -383,7 +383,7 @@ public function title(bool $allWords = false): parent return $str; } - public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); @@ -396,7 +396,7 @@ public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent return $str; } - public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); @@ -409,7 +409,7 @@ public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): pare return $str; } - public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { throw new InvalidArgumentException('Invalid UTF-8 chars.'); @@ -422,7 +422,7 @@ public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): pa return $str; } - public function upper(): parent + public function upper(): static { $str = clone $this; $str->string = mb_strtoupper($str->string, 'UTF-8'); diff --git a/ByteString.php b/ByteString.php index 63183bd..eaf0967 100644 --- a/ByteString.php +++ b/ByteString.php @@ -92,7 +92,7 @@ public function bytesAt(int $offset): array return '' === $str ? [] : [\ord($str)]; } - public function append(string ...$suffix): parent + public function append(string ...$suffix): static { $str = clone $this; $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); @@ -100,7 +100,7 @@ public function append(string ...$suffix): parent return $str; } - public function camel(): parent + public function camel(): static { $str = clone $this; $str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); @@ -155,7 +155,7 @@ public function equalsTo(string|iterable|AbstractString $string): bool return $string === $this->string; } - public function folded(): parent + public function folded(): static { $str = clone $this; $str->string = strtolower($str->string); @@ -202,7 +202,7 @@ public function isUtf8(): bool return '' === $this->string || preg_match('//u', $this->string); } - public function join(array $strings, string $lastGlue = null): parent + public function join(array $strings, string $lastGlue = null): static { $str = clone $this; @@ -217,7 +217,7 @@ public function length(): int return \strlen($this->string); } - public function lower(): parent + public function lower(): static { $str = clone $this; $str->string = strtolower($str->string); @@ -254,7 +254,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array return $matches; } - public function padBoth(int $length, string $padStr = ' '): parent + public function padBoth(int $length, string $padStr = ' '): static { $str = clone $this; $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); @@ -262,7 +262,7 @@ public function padBoth(int $length, string $padStr = ' '): parent return $str; } - public function padEnd(int $length, string $padStr = ' '): parent + public function padEnd(int $length, string $padStr = ' '): static { $str = clone $this; $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); @@ -270,7 +270,7 @@ public function padEnd(int $length, string $padStr = ' '): parent return $str; } - public function padStart(int $length, string $padStr = ' '): parent + public function padStart(int $length, string $padStr = ' '): static { $str = clone $this; $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); @@ -278,7 +278,7 @@ public function padStart(int $length, string $padStr = ' '): parent return $str; } - public function prepend(string ...$prefix): parent + public function prepend(string ...$prefix): static { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; @@ -286,7 +286,7 @@ public function prepend(string ...$prefix): parent return $str; } - public function replace(string $from, string $to): parent + public function replace(string $from, string $to): static { $str = clone $this; @@ -297,7 +297,7 @@ public function replace(string $from, string $to): parent return $str; } - public function replaceMatches(string $fromRegexp, string|callable $to): parent + public function replaceMatches(string $fromRegexp, string|callable $to): static { if ($this->ignoreCase) { $fromRegexp .= 'i'; @@ -337,7 +337,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): parent return $str; } - public function reverse(): parent + public function reverse(): static { $str = clone $this; $str->string = strrev($str->string); @@ -345,7 +345,7 @@ public function reverse(): parent return $str; } - public function slice(int $start = 0, int $length = null): parent + public function slice(int $start = 0, int $length = null): static { $str = clone $this; $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); @@ -353,7 +353,7 @@ public function slice(int $start = 0, int $length = null): parent return $str; } - public function snake(): parent + public function snake(): static { $str = $this->camel()->title(); $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string)); @@ -361,7 +361,7 @@ public function snake(): parent return $str; } - public function splice(string $replacement, int $start = 0, int $length = null): parent + public function splice(string $replacement, int $start = 0, int $length = null): static { $str = clone $this; $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); @@ -407,7 +407,7 @@ public function startsWith(string|iterable|AbstractString $prefix): bool return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix))); } - public function title(bool $allWords = false): parent + public function title(bool $allWords = false): static { $str = clone $this; $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string); @@ -457,7 +457,7 @@ public function toCodePointString(string $fromEncoding = null): CodePointString return $u; } - public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent + public function trim(string $chars = " \t\n\r\0\x0B\x0C"): static { $str = clone $this; $str->string = trim($str->string, $chars); @@ -465,7 +465,7 @@ public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent return $str; } - public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static { $str = clone $this; $str->string = rtrim($str->string, $chars); @@ -473,7 +473,7 @@ public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent return $str; } - public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static { $str = clone $this; $str->string = ltrim($str->string, $chars); @@ -481,7 +481,7 @@ public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent return $str; } - public function upper(): parent + public function upper(): static { $str = clone $this; $str->string = strtoupper($str->string); diff --git a/CodePointString.php b/CodePointString.php index de52a82..926ff79 100644 --- a/CodePointString.php +++ b/CodePointString.php @@ -33,7 +33,7 @@ public function __construct(string $string = '') $this->string = $string; } - public function append(string ...$suffix): AbstractString + public function append(string ...$suffix): static { $str = clone $this; $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); @@ -153,7 +153,7 @@ public function length(): int return mb_strlen($this->string, 'UTF-8'); } - public function prepend(string ...$prefix): AbstractString + public function prepend(string ...$prefix): static { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; @@ -165,7 +165,7 @@ public function prepend(string ...$prefix): AbstractString return $str; } - public function replace(string $from, string $to): AbstractString + public function replace(string $from, string $to): static { $str = clone $this; @@ -186,7 +186,7 @@ public function replace(string $from, string $to): AbstractString return $str; } - public function slice(int $start = 0, int $length = null): AbstractString + public function slice(int $start = 0, int $length = null): static { $str = clone $this; $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); @@ -194,7 +194,7 @@ public function slice(int $start = 0, int $length = null): AbstractString return $str; } - public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + public function splice(string $replacement, int $start = 0, int $length = null): static { if (!preg_match('//u', $replacement)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); diff --git a/LazyString.php b/LazyString.php index ba16888..9a9f94f 100644 --- a/LazyString.php +++ b/LazyString.php @@ -25,7 +25,7 @@ class LazyString implements \Stringable, \JsonSerializable * * @return static */ - public static function fromCallable(callable|array $callback, mixed ...$arguments): self + public static function fromCallable(callable|array $callback, mixed ...$arguments): static { if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) instanceof \Closure || 2 < \count($callback))) { throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']')); @@ -52,7 +52,7 @@ public static function fromCallable(callable|array $callback, mixed ...$argument /** * @return static */ - public static function fromStringable(string|int|float|bool|\Stringable $value): self + public static function fromStringable(string|int|float|bool|\Stringable $value): static { if (\is_object($value)) { return static::fromCallable([$value, '__toString']); diff --git a/UnicodeString.php b/UnicodeString.php index b3d4f50..a33d90d 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -41,7 +41,7 @@ public function __construct(string $string = '') } } - public function append(string ...$suffix): AbstractString + public function append(string ...$suffix): static { $str = clone $this; $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); @@ -176,7 +176,7 @@ public function indexOfLast(string|iterable|AbstractString $needle, int $offset return false === $i ? null : $i; } - public function join(array $strings, string $lastGlue = null): AbstractString + public function join(array $strings, string $lastGlue = null): static { $str = parent::join($strings, $lastGlue); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); @@ -192,7 +192,7 @@ public function length(): int /** * @return static */ - public function normalize(int $form = self::NFC): parent + public function normalize(int $form = self::NFC): static { $str = clone $this; @@ -208,7 +208,7 @@ public function normalize(int $form = self::NFC): parent return $str; } - public function prepend(string ...$prefix): AbstractString + public function prepend(string ...$prefix): static { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; @@ -221,7 +221,7 @@ public function prepend(string ...$prefix): AbstractString return $str; } - public function replace(string $from, string $to): AbstractString + public function replace(string $from, string $to): static { $str = clone $this; normalizer_is_normalized($from) ?: $from = normalizer_normalize($from); @@ -248,7 +248,7 @@ public function replace(string $from, string $to): AbstractString return $str; } - public function replaceMatches(string $fromRegexp, string|callable $to): AbstractString + public function replaceMatches(string $fromRegexp, string|callable $to): static { $str = parent::replaceMatches($fromRegexp, $to); normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); @@ -256,7 +256,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): Abstrac return $str; } - public function slice(int $start = 0, int $length = null): AbstractString + public function slice(int $start = 0, int $length = null): static { $str = clone $this; @@ -265,7 +265,7 @@ public function slice(int $start = 0, int $length = null): AbstractString return $str; } - public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + public function splice(string $replacement, int $start = 0, int $length = null): static { $str = clone $this; From 6fa206caaead808ac601680cada5ac54a5aab70e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 16 Aug 2021 18:31:32 +0200 Subject: [PATCH 14/68] Run php-cs-fixer --- AbstractString.php | 88 --------------------------------------- AbstractUnicodeString.php | 6 --- LazyString.php | 5 --- UnicodeString.php | 3 -- 4 files changed, 102 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index 71a981e..da52099 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -92,8 +92,6 @@ public static function wrap(array $values): array /** * @param string|string[] $needle - * - * @return static */ public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { @@ -127,8 +125,6 @@ public function after(string|iterable $needle, bool $includeNeedle = false, int /** * @param string|string[] $needle - * - * @return static */ public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { @@ -160,15 +156,10 @@ public function afterLast(string|iterable $needle, bool $includeNeedle = false, return $this->slice($i); } - /** - * @return static - */ abstract public function append(string ...$suffix): static; /** * @param string|string[] $needle - * - * @return static */ public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { @@ -202,8 +193,6 @@ public function before(string|iterable $needle, bool $includeNeedle = false, int /** * @param string|string[] $needle - * - * @return static */ public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static { @@ -245,9 +234,6 @@ public function bytesAt(int $offset): array return '' === $str->string ? [] : array_values(unpack('C*', $str->string)); } - /** - * @return static - */ abstract public function camel(): static; /** @@ -255,9 +241,6 @@ abstract public function camel(): static; */ abstract public function chunk(int $length = 1): array; - /** - * @return static - */ public function collapseWhitespace(): static { $str = clone $this; @@ -292,9 +275,6 @@ public function endsWith(string|iterable $suffix): bool return false; } - /** - * @return static - */ public function ensureEnd(string $suffix): static { if (!$this->endsWith($suffix)) { @@ -307,9 +287,6 @@ public function ensureEnd(string $suffix): static return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); } - /** - * @return static - */ public function ensureStart(string $prefix): static { $prefix = new static($prefix); @@ -347,14 +324,8 @@ public function equalsTo(string|iterable $string): bool return false; } - /** - * @return static - */ abstract public function folded(): static; - /** - * @return static - */ public function ignoreCase(): static { $str = clone $this; @@ -412,9 +383,6 @@ public function isEmpty(): bool return '' === $this->string; } - /** - * @return static - */ abstract public function join(array $strings, string $lastGlue = null): static; public function jsonSerialize(): string @@ -424,9 +392,6 @@ public function jsonSerialize(): string abstract public function length(): int; - /** - * @return static - */ abstract public function lower(): static; /** @@ -438,29 +403,14 @@ abstract public function lower(): static; */ abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array; - /** - * @return static - */ abstract public function padBoth(int $length, string $padStr = ' '): static; - /** - * @return static - */ abstract public function padEnd(int $length, string $padStr = ' '): static; - /** - * @return static - */ abstract public function padStart(int $length, string $padStr = ' '): static; - /** - * @return static - */ abstract public function prepend(string ...$prefix): static; - /** - * @return static - */ public function repeat(int $multiplier): static { if (0 > $multiplier) { @@ -473,36 +423,19 @@ public function repeat(int $multiplier): static return $str; } - /** - * @return static - */ abstract public function replace(string $from, string $to): static; /** * @param string|callable $to - * - * @return static */ abstract public function replaceMatches(string $fromRegexp, string|callable $to): static; - /** - * @return static - */ abstract public function reverse(): static; - /** - * @return static - */ abstract public function slice(int $start = 0, int $length = null): static; - /** - * @return static - */ abstract public function snake(): static; - /** - * @return static - */ abstract public function splice(string $replacement, int $start = 0, int $length = null): static; /** @@ -571,9 +504,6 @@ public function startsWith(string|iterable $prefix): bool return false; } - /** - * @return static - */ abstract public function title(bool $allWords = false): static; public function toByteString(string $toEncoding = null): ByteString @@ -622,24 +552,12 @@ public function toUnicodeString(): UnicodeString return new UnicodeString($this->string); } - /** - * @return static - */ abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; - /** - * @return static - */ abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; - /** - * @return static - */ abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static; - /** - * @return static - */ public function truncate(int $length, string $ellipsis = '', bool $cut = true): static { $stringLength = $this->length(); @@ -667,9 +585,6 @@ public function truncate(int $length, string $ellipsis = '', bool $cut = true): return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; } - /** - * @return static - */ abstract public function upper(): static; /** @@ -677,9 +592,6 @@ abstract public function upper(): static; */ abstract public function width(bool $ignoreAnsiDecoration = true): int; - /** - * @return static - */ public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static { $lines = '' !== $break ? $this->split($break) : [clone $this]; diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index dc85c67..cb9f2c5 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -50,9 +50,6 @@ abstract class AbstractUnicodeString extends AbstractString private static $transliterators = []; - /** - * @return static - */ public static function fromCodePoints(int ...$codes): static { $string = ''; @@ -252,9 +249,6 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array return $matches; } - /** - * @return static - */ public function normalize(int $form = self::NFC): static { if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { diff --git a/LazyString.php b/LazyString.php index 9a9f94f..15b8d72 100644 --- a/LazyString.php +++ b/LazyString.php @@ -22,8 +22,6 @@ class LazyString implements \Stringable, \JsonSerializable /** * @param callable|array $callback A callable or a [Closure, method] lazy-callable - * - * @return static */ public static function fromCallable(callable|array $callback, mixed ...$arguments): static { @@ -49,9 +47,6 @@ public static function fromCallable(callable|array $callback, mixed ...$argument return $lazyString; } - /** - * @return static - */ public static function fromStringable(string|int|float|bool|\Stringable $value): static { if (\is_object($value)) { diff --git a/UnicodeString.php b/UnicodeString.php index a33d90d..70cf4c5 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -189,9 +189,6 @@ public function length(): int return grapheme_strlen($this->string); } - /** - * @return static - */ public function normalize(int $form = self::NFC): static { $str = clone $this; From f1e8ad774bb7eab498ef4252e59302a624f55478 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 9 Sep 2021 08:54:05 +0200 Subject: [PATCH 15/68] Remove needless TypeErrors --- AbstractUnicodeString.php | 4 ---- ByteString.php | 10 +--------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index cb9f2c5..d285f2b 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -304,10 +304,6 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static } if (\is_array($to) || $to instanceof \Closure) { - if (!\is_callable($to)) { - throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); - } - $replace = 'preg_replace_callback'; $to = static function (array $m) use ($to): string { $to = $to($m); diff --git a/ByteString.php b/ByteString.php index eaf0967..b3649b6 100644 --- a/ByteString.php +++ b/ByteString.php @@ -303,15 +303,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static $fromRegexp .= 'i'; } - if (\is_array($to)) { - if (!\is_callable($to)) { - throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); - } - - $replace = 'preg_replace_callback'; - } else { - $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; - } + $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); From 2f80632fddeb6932bc5339910c5638da68dcceeb Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Wed, 8 Dec 2021 14:13:04 +0100 Subject: [PATCH 16/68] Leverage str_starts_with(), str_ends_with() and str_contains() --- AbstractString.php | 2 +- AbstractUnicodeString.php | 6 +++--- ByteString.php | 4 ++-- Inflector/EnglishInflector.php | 4 ++-- LazyString.php | 2 +- Slugger/AsciiSlugger.php | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index b7e92f5..0c17f9e 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -458,7 +458,7 @@ public function split(string $delimiter, int $limit = null, int $flags = null): $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && '_ERROR' === substr($k, -6)) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { throw new RuntimeException('Splitting failed with '.$k.'.'); } } diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 4e586d7..ae714b0 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -235,7 +235,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && '_ERROR' === substr($k, -6)) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { throw new RuntimeException('Matching failed with '.$k.'.'); } } @@ -327,7 +327,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && '_ERROR' === substr($k, -6)) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { throw new RuntimeException('Matching failed with '.$k.'.'); } } @@ -465,7 +465,7 @@ public function width(bool $ignoreAnsiDecoration = true): int $width = 0; $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); - if (false !== strpos($s, "\r")) { + if (str_contains($s, "\r")) { $s = str_replace(["\r\n", "\r"], "\n", $s); } diff --git a/ByteString.php b/ByteString.php index b3649b6..05ec690 100644 --- a/ByteString.php +++ b/ByteString.php @@ -240,7 +240,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && '_ERROR' === substr($k, -6)) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { throw new RuntimeException('Matching failed with '.$k.'.'); } } @@ -312,7 +312,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static $lastError = preg_last_error(); foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && '_ERROR' === substr($k, -6)) { + if ($lastError === $v && str_ends_with($k, '_ERROR')) { throw new RuntimeException('Matching failed with '.$k.'.'); } } diff --git a/Inflector/EnglishInflector.php b/Inflector/EnglishInflector.php index 9f2fac6..d9cc8e3 100644 --- a/Inflector/EnglishInflector.php +++ b/Inflector/EnglishInflector.php @@ -384,7 +384,7 @@ public function singularize(string $plural): array if ($j === $suffixLength) { // Is there any character preceding the suffix in the plural string? if ($j < $pluralLength) { - $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]); + $nextIsVocal = str_contains('aeiou', $lowerPluralRev[$j]); if (!$map[2] && $nextIsVocal) { // suffix may not succeed a vocal but next char is one @@ -464,7 +464,7 @@ public function pluralize(string $singular): array if ($j === $suffixLength) { // Is there any character preceding the suffix in the plural string? if ($j < $singularLength) { - $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]); + $nextIsVocal = str_contains('aeiou', $lowerSingularRev[$j]); if (!$map[2] && $nextIsVocal) { // suffix may not succeed a vocal but next char is one diff --git a/LazyString.php b/LazyString.php index 15b8d72..0920899 100644 --- a/LazyString.php +++ b/LazyString.php @@ -127,7 +127,7 @@ private static function getPrettyName(callable $callback): string } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); - if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) { + if (str_contains($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) { return $r->name; } diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 548a6b9..77b1e13 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -96,7 +96,7 @@ public function slug(string $string, string $separator = '-', string $locale = n $locale = $locale ?? $this->defaultLocale; $transliterator = []; - if ($locale && ('de' === $locale || 0 === strpos($locale, 'de_'))) { + if ($locale && ('de' === $locale || str_starts_with($locale, 'de_'))) { // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl) $transliterator = ['de-ASCII']; } elseif (\function_exists('transliterator_transliterate') && $locale) { From 0cfed595758ec6e0a25591bdc8ca733c1896af32 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 7 Dec 2021 12:27:08 +0100 Subject: [PATCH 17/68] Remove FQCN type hints on properties --- Resources/WcswidthDataGenerator.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Resources/WcswidthDataGenerator.php b/Resources/WcswidthDataGenerator.php index 10e68d9..96d73bf 100644 --- a/Resources/WcswidthDataGenerator.php +++ b/Resources/WcswidthDataGenerator.php @@ -14,7 +14,6 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\String\Exception\RuntimeException; use Symfony\Component\VarExporter\VarExporter; -use Symfony\Contracts\HttpClient\HttpClientInterface; /** * @internal @@ -22,7 +21,7 @@ final class WcswidthDataGenerator { private string $outDir; - private HttpClientInterface $client; + private $client; public function __construct(string $outDir) { From e2dbcc71317cc33681f06c2b0c1099b9f6847fc0 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 9 Dec 2021 13:46:12 +0100 Subject: [PATCH 18/68] Revert "bug #44494 Remove FQCN type hints on properties (fabpot)" This reverts commit 0a13eba7037c6636be7a35c194d7d2e86d6ea6ba, reversing changes made to e4cfa82c31ffda491b73e181c8d3d4e0a307eee8. --- Resources/WcswidthDataGenerator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Resources/WcswidthDataGenerator.php b/Resources/WcswidthDataGenerator.php index 96d73bf..10e68d9 100644 --- a/Resources/WcswidthDataGenerator.php +++ b/Resources/WcswidthDataGenerator.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\String\Exception\RuntimeException; use Symfony\Component\VarExporter\VarExporter; +use Symfony\Contracts\HttpClient\HttpClientInterface; /** * @internal @@ -21,7 +22,7 @@ final class WcswidthDataGenerator { private string $outDir; - private $client; + private HttpClientInterface $client; public function __construct(string $outDir) { From bae261d0c3ac38a1f802b4dfed42094296100631 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 23:13:01 +0100 Subject: [PATCH 19/68] [6.0] cs fixes --- AbstractString.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index b7e92f5..8564430 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -425,9 +425,6 @@ public function repeat(int $multiplier): static abstract public function replace(string $from, string $to): static; - /** - * @param string|callable $to - */ abstract public function replaceMatches(string $fromRegexp, string|callable $to): static; abstract public function reverse(): static; From 532df62df0dd7c4cdfec596edea6c6c854f0a509 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 11:11:51 +0100 Subject: [PATCH 20/68] Add more nullsafe operators --- AbstractString.php | 2 +- ByteString.php | 4 ++-- CodePointString.php | 2 +- Resources/functions.php | 2 +- Slugger/AsciiSlugger.php | 2 +- UnicodeString.php | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index cdad383..b66e3d2 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -74,7 +74,7 @@ public static function wrap(array $values): array foreach ($values as $k => $v) { if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) { - $keys = $keys ?? array_keys($values); + $keys ??= array_keys($values); $keys[$i] = $j; } diff --git a/ByteString.php b/ByteString.php index 05ec690..21bbf4a 100644 --- a/ByteString.php +++ b/ByteString.php @@ -48,7 +48,7 @@ public static function fromRandom(int $length = 16, string $alphabet = null): se throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); } - $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; + $alphabet ??= self::ALPHABET_ALPHANUMERIC; $alphabetSize = \strlen($alphabet); $bits = (int) ceil(log($alphabetSize, 2.0)); if ($bits <= 0 || $bits > 56) { @@ -363,7 +363,7 @@ public function splice(string $replacement, int $start = 0, int $length = null): public function split(string $delimiter, int $limit = null, int $flags = null): array { - if (1 > $limit = $limit ?? \PHP_INT_MAX) { + if (1 > $limit ??= \PHP_INT_MAX) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } diff --git a/CodePointString.php b/CodePointString.php index 926ff79..f5c900f 100644 --- a/CodePointString.php +++ b/CodePointString.php @@ -210,7 +210,7 @@ public function splice(string $replacement, int $start = 0, int $length = null): public function split(string $delimiter, int $limit = null, int $flags = null): array { - if (1 > $limit = $limit ?? \PHP_INT_MAX) { + if (1 > $limit ??= \PHP_INT_MAX) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } diff --git a/Resources/functions.php b/Resources/functions.php index c950894..7a97040 100644 --- a/Resources/functions.php +++ b/Resources/functions.php @@ -31,7 +31,7 @@ function b(?string $string = ''): ByteString */ function s(?string $string = ''): AbstractString { - $string = $string ?? ''; + $string ??= ''; return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); } diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 77b1e13..d32e305 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -93,7 +93,7 @@ public function getLocale(): string */ public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString { - $locale = $locale ?? $this->defaultLocale; + $locale ??= $this->defaultLocale; $transliterator = []; if ($locale && ('de' === $locale || str_starts_with($locale, 'de_'))) { diff --git a/UnicodeString.php b/UnicodeString.php index 70cf4c5..47a708b 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -280,7 +280,7 @@ public function splice(string $replacement, int $start = 0, int $length = null): public function split(string $delimiter, int $limit = null, int $flags = null): array { - if (1 > $limit = $limit ?? 2147483647) { + if (1 > $limit ??= 2147483647) { throw new InvalidArgumentException('Split limit must be a positive integer.'); } From 6e0bf37c9c4826549e1c601123aa9d1b36b4f36e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Feb 2022 15:00:38 +0100 Subject: [PATCH 21/68] Bump minimum version of PHP to 8.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 187323f..5e4febb 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", From db0b16bfea9198f5cf852fb7d6d346e45a22d939 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 31 Mar 2022 18:23:12 +0200 Subject: [PATCH 22/68] Leverage non-capturing catches --- UnicodeString.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnicodeString.php b/UnicodeString.php index 47a708b..a64c6a9 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -139,7 +139,7 @@ public function indexOf(string|iterable|AbstractString $needle, int $offset = 0) try { $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset); - } catch (\ValueError $e) { + } catch (\ValueError) { return null; } From 20a20a4a64f02d5415a5c11e34ac451425e085dd Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 8 Apr 2022 18:15:24 +0200 Subject: [PATCH 23/68] Leverage array_is_list(), get_debug_type(), is_countable(), is_iterable(), str_contains() and str_starts_with() --- AbstractString.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index b66e3d2..75c9d33 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -558,7 +558,7 @@ abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF */ public function trimPrefix($prefix): static { - if (\is_array($prefix) || $prefix instanceof \Traversable) { + if (is_iterable($prefix)) { foreach ($prefix as $s) { $t = $this->trimPrefix($s); @@ -592,7 +592,7 @@ abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FE */ public function trimSuffix($suffix): static { - if (\is_array($suffix) || $suffix instanceof \Traversable) { + if (is_iterable($suffix)) { foreach ($suffix as $s) { $t = $this->trimSuffix($s); From f0d5d33a56ecba3c1495db31065414fbb3279eb0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 12 Apr 2022 16:39:55 +0200 Subject: [PATCH 24/68] [Cache][String] Don't use is_iterable() in perf-critical code paths --- AbstractString.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index 75c9d33..fcab13a 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -558,7 +558,7 @@ abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF */ public function trimPrefix($prefix): static { - if (is_iterable($prefix)) { + if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow foreach ($prefix as $s) { $t = $this->trimPrefix($s); @@ -592,7 +592,7 @@ abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FE */ public function trimSuffix($suffix): static { - if (is_iterable($suffix)) { + if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow foreach ($suffix as $s) { $t = $this->trimSuffix($s); From 910c205cfbde957bf79dfe04ee032a48b6b002f4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 24 Jun 2022 14:33:15 +0200 Subject: [PATCH 25/68] Use preg_last_error_msg() when possible --- AbstractString.php | 10 +--------- AbstractUnicodeString.php | 10 +--------- ByteString.php | 10 +--------- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index fcab13a..85962cf 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -452,15 +452,7 @@ public function split(string $delimiter, int $limit = null, int $flags = null): try { if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { - $lastError = preg_last_error(); - - foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && str_ends_with($k, '_ERROR')) { - throw new RuntimeException('Splitting failed with '.$k.'.'); - } - } - - throw new RuntimeException('Splitting failed with unknown error code.'); + throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg()); } } finally { restore_error_handler(); diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index b2906e2..663f19c 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -234,15 +234,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array try { if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { - $lastError = preg_last_error(); - - foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && str_ends_with($k, '_ERROR')) { - throw new RuntimeException('Matching failed with '.$k.'.'); - } - } - - throw new RuntimeException('Matching failed with unknown error code.'); + throw new RuntimeException('Matching failed with error: '.preg_last_error_msg()); } } finally { restore_error_handler(); diff --git a/ByteString.php b/ByteString.php index 21bbf4a..a399412 100644 --- a/ByteString.php +++ b/ByteString.php @@ -237,15 +237,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array try { if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { - $lastError = preg_last_error(); - - foreach (get_defined_constants(true)['pcre'] as $k => $v) { - if ($lastError === $v && str_ends_with($k, '_ERROR')) { - throw new RuntimeException('Matching failed with '.$k.'.'); - } - } - - throw new RuntimeException('Matching failed with unknown error code.'); + throw new RuntimeException('Matching failed with error: '.preg_last_error_msg()); } } finally { restore_error_handler(); From 30bee3727012cbed79b41e54c1bcfb50ec3c690a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 20 Jul 2022 15:55:56 +0200 Subject: [PATCH 26/68] Fix CS --- Tests/FunctionsTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/FunctionsTest.php b/Tests/FunctionsTest.php index a721d85..64bfdfc 100644 --- a/Tests/FunctionsTest.php +++ b/Tests/FunctionsTest.php @@ -14,12 +14,13 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\String\AbstractString; use Symfony\Component\String\ByteString; -use Symfony\Component\String\UnicodeString; use function Symfony\Component\String\b; use function Symfony\Component\String\s; use function Symfony\Component\String\u; +use Symfony\Component\String\UnicodeString; + final class FunctionsTest extends TestCase { /** From 497baffcbe9a43956259da52bd148d3a2bc9457d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 12 Aug 2022 14:58:51 +0200 Subject: [PATCH 27/68] [String] Add support for emoji in AsciiSlugger --- CHANGELOG.md | 5 +++ Slugger/AsciiSlugger.php | 25 +++++++++++++ Tests/Slugger/AsciiSluggerTest.php | 57 ++++++++++++++++++++++++++++-- composer.json | 1 + 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53af364..31a3b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Add support for emoji in `AsciiSlugger` + 5.4 --- diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index d32e305..d72b4e5 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -11,6 +11,7 @@ namespace Symfony\Component\String\Slugger; +use Symfony\Component\Intl\Transliterator\EmojiTransliterator; use Symfony\Component\String\AbstractUnicodeString; use Symfony\Component\String\UnicodeString; use Symfony\Contracts\Translation\LocaleAwareInterface; @@ -58,6 +59,7 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface private \Closure|array $symbolsMap = [ 'en' => ['@' => 'at', '&' => 'and'], ]; + private bool|string $emoji = false; /** * Cache of transliterators per locale. @@ -88,6 +90,23 @@ public function getLocale(): string return $this->defaultLocale; } + /** + * @param bool|string $emoji true will use the same locale, + * false will disable emoji, + * and a string to use a specific locale + */ + public function withEmoji(bool|string $emoji = true): static + { + if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { + throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__)); + } + + $new = clone $this; + $new->emoji = $emoji; + + return $new; + } + /** * {@inheritdoc} */ @@ -103,6 +122,12 @@ public function slug(string $string, string $separator = '-', string $locale = n $transliterator = (array) $this->createTransliterator($locale); } + if (\is_string($this->emoji)) { + $transliterator[] = EmojiTransliterator::create("emoji-{$this->emoji}"); + } elseif ($this->emoji && null !== $locale) { + $transliterator[] = EmojiTransliterator::create("emoji-{$locale}"); + } + if ($this->symbolsMap instanceof \Closure) { // If the symbols map is passed as a closure, there is no need to fallback to the parent locale // as the closure can just provide substitutions for all locales of interest. diff --git a/Tests/Slugger/AsciiSluggerTest.php b/Tests/Slugger/AsciiSluggerTest.php index d58c002..37eba92 100644 --- a/Tests/Slugger/AsciiSluggerTest.php +++ b/Tests/Slugger/AsciiSluggerTest.php @@ -16,6 +16,16 @@ class AsciiSluggerTest extends TestCase { + /** + * @dataProvider provideSlugTests + */ + public function testSlug(string $expected, string $string, string $separator = '-', string $locale = null) + { + $slugger = new AsciiSlugger(); + + $this->assertSame($expected, (string) $slugger->slug($string, $separator, $locale)); + } + public function provideSlugTests(): iterable { yield ['', '']; @@ -37,11 +47,52 @@ public function provideSlugTests(): iterable yield [\function_exists('transliterator_transliterate') ? 'gh' : '', 'ғ', '-', 'uz_fr']; // Ensure we get the parent locale } - /** @dataProvider provideSlugTests */ - public function testSlug(string $expected, string $string, string $separator = '-', string $locale = null) + /** + * @dataProvider provideSlugEmojiTests + * @requires extension intl + */ + public function testSlugEmoji(string $expected, string $string, ?string $locale, string|bool $emoji = true) { $slugger = new AsciiSlugger(); + $slugger = $slugger->withEmoji($emoji); - $this->assertSame($expected, (string) $slugger->slug($string, $separator, $locale)); + $this->assertSame($expected, (string) $slugger->slug($string, '-', $locale)); + } + + public function provideSlugEmojiTests(): iterable + { + yield [ + 'un-chat-qui-sourit-chat-noir-et-un-tete-de-lion-vont-au-parc-national', + 'un 😺, 🐈‍⬛, et un 🦁 vont au 🏞️', + 'fr', + ]; + yield [ + 'a-grinning-cat-black-cat-and-a-lion-go-to-national-park-smiling-face-with-heart-eyes-party-popper-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + 'en', + ]; + yield [ + 'a-and-a-go-to', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + null, + ]; + yield [ + 'a-smiley-cat-black-cat-and-a-lion-face-go-to-national-park-heart-eyes-tada-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + null, + 'slack', + ]; + yield [ + 'a-smiley-cat-black-cat-and-a-lion-go-to-national-park-heart-eyes-tada-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + null, + 'github', + ]; + yield [ + 'a-smiley-cat-black-cat-and-a-lion-go-to-national-park-heart-eyes-tada-yellow-heart', + 'a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', + 'en', + 'github', + ]; } } diff --git a/composer.json b/composer.json index 5e4febb..44a809d 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ }, "require-dev": { "symfony/error-handler": "^5.4|^6.0", + "symfony/intl": "^6.2", "symfony/http-client": "^5.4|^6.0", "symfony/translation-contracts": "^2.0|^3.0", "symfony/var-exporter": "^5.4|^6.0" From e6bbed187a4a3c3b612f06650ea7cb6a11b4debd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 25 Aug 2022 16:59:21 +0200 Subject: [PATCH 28/68] [CS] Remove @inheritdoc PHPDoc --- Inflector/EnglishInflector.php | 6 ------ Inflector/FrenchInflector.php | 6 ------ Slugger/AsciiSlugger.php | 9 --------- 3 files changed, 21 deletions(-) diff --git a/Inflector/EnglishInflector.php b/Inflector/EnglishInflector.php index d9cc8e3..4474736 100644 --- a/Inflector/EnglishInflector.php +++ b/Inflector/EnglishInflector.php @@ -350,9 +350,6 @@ final class EnglishInflector implements InflectorInterface 'seiceps', ]; - /** - * {@inheritdoc} - */ public function singularize(string $plural): array { $pluralRev = strrev($plural); @@ -429,9 +426,6 @@ public function singularize(string $plural): array return [$plural]; } - /** - * {@inheritdoc} - */ public function pluralize(string $singular): array { $singularRev = strrev($singular); diff --git a/Inflector/FrenchInflector.php b/Inflector/FrenchInflector.php index 612c8f2..955abbf 100644 --- a/Inflector/FrenchInflector.php +++ b/Inflector/FrenchInflector.php @@ -110,9 +110,6 @@ final class FrenchInflector implements InflectorInterface */ private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i'; - /** - * {@inheritdoc} - */ public function singularize(string $plural): array { if ($this->isInflectedWord($plural)) { @@ -130,9 +127,6 @@ public function singularize(string $plural): array return [$plural]; } - /** - * {@inheritdoc} - */ public function pluralize(string $singular): array { if ($this->isInflectedWord($singular)) { diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index d72b4e5..f4b44d3 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -74,17 +74,11 @@ public function __construct(string $defaultLocale = null, array|\Closure $symbol $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } - /** - * {@inheritdoc} - */ public function setLocale(string $locale) { $this->defaultLocale = $locale; } - /** - * {@inheritdoc} - */ public function getLocale(): string { return $this->defaultLocale; @@ -107,9 +101,6 @@ public function withEmoji(bool|string $emoji = true): static return $new; } - /** - * {@inheritdoc} - */ public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString { $locale ??= $this->defaultLocale; From b3be028dfaddea730f7ac806d76f4389fc9471be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20de=20Guillebon?= Date: Fri, 26 Aug 2022 16:19:22 +0200 Subject: [PATCH 29/68] Replace get_class() calls by ::class --- LazyString.php | 2 +- Resources/bin/update-data.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LazyString.php b/LazyString.php index 84fa7bf..3b91c4f 100644 --- a/LazyString.php +++ b/LazyString.php @@ -86,7 +86,7 @@ public function __toString(): string try { return $this->value = ($this->value)(); } catch (\Throwable $e) { - if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) { + if (\TypeError::class === $e::class && __FILE__ === $e->getFile()) { $type = explode(', ', $e->getMessage()); $type = substr(array_pop($type), 0, -\strlen(' returned')); $r = new \ReflectionFunction($this->value); diff --git a/Resources/bin/update-data.php b/Resources/bin/update-data.php index 3f66be2..edbe8a4 100644 --- a/Resources/bin/update-data.php +++ b/Resources/bin/update-data.php @@ -28,7 +28,7 @@ echo "Caused by\n"; } - echo get_class($cause).': '.$cause->getMessage()."\n"; + echo $cause::class.': '.$cause->getMessage()."\n"; echo "\n"; echo $cause->getFile().':'.$cause->getLine()."\n"; echo $cause->getTraceAsString()."\n"; From bc8ad48346172676a53dcab896275984ad7226ed Mon Sep 17 00:00:00 2001 From: tigitz Date: Wed, 21 Sep 2022 16:43:57 +0200 Subject: [PATCH 30/68] Add a few more ??= --- LazyString.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyString.php b/LazyString.php index 3b91c4f..7bc3a03 100644 --- a/LazyString.php +++ b/LazyString.php @@ -34,7 +34,7 @@ public static function fromCallable(callable|array $callback, mixed ...$argument if (null !== $arguments) { if (!\is_callable($callback)) { $callback[0] = $callback[0](); - $callback[1] = $callback[1] ?? '__invoke'; + $callback[1] ??= '__invoke'; } $value = $callback(...$arguments); $callback = self::getPrettyName($callback); From f9d020812f148fc45663725d8d5ba94281455025 Mon Sep 17 00:00:00 2001 From: tigitz Date: Fri, 30 Sep 2022 22:34:56 +0200 Subject: [PATCH 31/68] Leverage First-class callable syntax --- LazyString.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyString.php b/LazyString.php index 7bc3a03..77d50fb 100644 --- a/LazyString.php +++ b/LazyString.php @@ -50,7 +50,7 @@ public static function fromCallable(callable|array $callback, mixed ...$argument public static function fromStringable(string|int|float|bool|\Stringable $value): static { if (\is_object($value)) { - return static::fromCallable([$value, '__toString']); + return static::fromCallable($value->__toString(...)); } $lazyString = new static(); From ba9fd9b020a450d07d02972a3d450d7895f14066 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 1 Nov 2022 22:49:27 +0100 Subject: [PATCH 32/68] Use ??= more --- AbstractUnicodeString.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 47338c6..5291227 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -121,10 +121,10 @@ public function ascii(array $rules = []): self $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s); $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s); } elseif (\function_exists('transliterator_transliterate')) { - if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) { + if (null === $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule)) { if ('any-latin/bgn' === $rule) { $rule = 'any-latin'; - $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule); + $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule); } if (null === $transliterator) { @@ -550,9 +550,7 @@ private function wcswidth(string $string): int return -1; } - if (null === self::$tableZero) { - self::$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php'; - } + self::$tableZero ??= require __DIR__.'/Resources/data/wcswidth_table_zero.php'; if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { $lbound = 0; @@ -569,9 +567,7 @@ private function wcswidth(string $string): int } } - if (null === self::$tableWide) { - self::$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php'; - } + self::$tableWide ??= require __DIR__.'/Resources/data/wcswidth_table_wide.php'; if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { $lbound = 0; From 145702685e0d12f81d755c71127bfff7582fdd36 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 29 Nov 2022 19:49:19 +0100 Subject: [PATCH 33/68] [String] Fix AsciiSlugger with emojis --- Slugger/AsciiSlugger.php | 25 +++++++++++++++++++++---- Tests/Slugger/AsciiSluggerTest.php | 11 +++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index f4b44d3..826d07c 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -113,10 +113,8 @@ public function slug(string $string, string $separator = '-', string $locale = n $transliterator = (array) $this->createTransliterator($locale); } - if (\is_string($this->emoji)) { - $transliterator[] = EmojiTransliterator::create("emoji-{$this->emoji}"); - } elseif ($this->emoji && null !== $locale) { - $transliterator[] = EmojiTransliterator::create("emoji-{$locale}"); + if ($emojiTransliterator = $this->createEmojiTransliterator($locale)) { + $transliterator[] = $emojiTransliterator; } if ($this->symbolsMap instanceof \Closure) { @@ -177,6 +175,25 @@ private function createTransliterator(string $locale): ?\Transliterator return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; } + private function createEmojiTransliterator(?string $locale): ?EmojiTransliterator + { + if (\is_string($this->emoji)) { + $locale = $this->emoji; + } elseif (!$this->emoji) { + return null; + } + + while (null !== $locale) { + try { + return EmojiTransliterator::create("emoji-$locale"); + } catch (\IntlException) { + $locale = self::getParentLocale($locale); + } + } + + return null; + } + private static function getParentLocale(?string $locale): ?string { if (!$locale) { diff --git a/Tests/Slugger/AsciiSluggerTest.php b/Tests/Slugger/AsciiSluggerTest.php index 37eba92..163be0e 100644 --- a/Tests/Slugger/AsciiSluggerTest.php +++ b/Tests/Slugger/AsciiSluggerTest.php @@ -49,6 +49,7 @@ public function provideSlugTests(): iterable /** * @dataProvider provideSlugEmojiTests + * * @requires extension intl */ public function testSlugEmoji(string $expected, string $string, ?string $locale, string|bool $emoji = true) @@ -94,5 +95,15 @@ public function provideSlugEmojiTests(): iterable 'en', 'github', ]; + yield [ + 'un-chat-qui-sourit-chat-noir-et-un-tete-de-lion-vont-au-parc-national', + 'un 😺, 🐈‍⬛, et un 🦁 vont au 🏞️', + 'fr_XX', // Fallback on parent locale + ]; + yield [ + 'un-et-un-vont-au', + 'un 😺, 🐈‍⬛, et un 🦁 vont au 🏞️', + 'undefined_locale', // Behaves the same as if emoji support is disabled + ]; } } From d0bd9e0cb4c307c9a25b711995c21252e9285ebf Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 28 Dec 2022 15:47:09 +0100 Subject: [PATCH 34/68] Drop v1 contracts packages everywhere --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 44a809d..3545c85 100644 --- a/composer.json +++ b/composer.json @@ -26,11 +26,11 @@ "symfony/error-handler": "^5.4|^6.0", "symfony/intl": "^6.2", "symfony/http-client": "^5.4|^6.0", - "symfony/translation-contracts": "^2.0|^3.0", + "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^5.4|^6.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "autoload": { "psr-4": { "Symfony\\Component\\String\\": "" }, From 319a0e9543092d6cb8d4d12f9362f36d0f79729e Mon Sep 17 00:00:00 2001 From: tigitz Date: Sun, 1 Jan 2023 19:45:34 +0100 Subject: [PATCH 35/68] Leverage arrow function syntax for closure --- AbstractUnicodeString.php | 4 +--- Resources/WcswidthDataGenerator.php | 4 +--- Slugger/AsciiSlugger.php | 4 +--- Tests/AbstractAsciiTestCase.php | 4 +--- Tests/AbstractUnicodeTestCase.php | 4 +--- Tests/LazyStringTest.php | 30 ++++++++++++++--------------- 6 files changed, 20 insertions(+), 30 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 5291227..0d8db3a 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -360,9 +360,7 @@ public function title(bool $allWords = false): static $limit = $allWords ? -1 : 1; - $str->string = preg_replace_callback('/\b./u', static function (array $m): string { - return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); - }, $str->string, $limit); + $str->string = preg_replace_callback('/\b./u', static fn (array $m): string => mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'), $str->string, $limit); return $str; } diff --git a/Resources/WcswidthDataGenerator.php b/Resources/WcswidthDataGenerator.php index 10e68d9..0526da1 100644 --- a/Resources/WcswidthDataGenerator.php +++ b/Resources/WcswidthDataGenerator.php @@ -104,9 +104,7 @@ private function format(array $rawData): array return [hexdec($start), hexdec($end)]; }, $rawData); - usort($data, static function (array $a, array $b): int { - return $a[0] - $b[0]; - }); + usort($data, static fn (array $a, array $b): int => $a[0] - $b[0]); return $data; } diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 826d07c..a573888 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -121,9 +121,7 @@ public function slug(string $string, string $separator = '-', string $locale = n // If the symbols map is passed as a closure, there is no need to fallback to the parent locale // as the closure can just provide substitutions for all locales of interest. $symbolsMap = $this->symbolsMap; - array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) { - return $symbolsMap($s, $locale); - }); + array_unshift($transliterator, static fn ($s) => $symbolsMap($s, $locale)); } $unicodeString = (new UnicodeString($string))->ascii($transliterator); diff --git a/Tests/AbstractAsciiTestCase.php b/Tests/AbstractAsciiTestCase.php index a0cf206..2801daa 100644 --- a/Tests/AbstractAsciiTestCase.php +++ b/Tests/AbstractAsciiTestCase.php @@ -993,9 +993,7 @@ public static function provideReplaceMatches() ['April,15,2003', 'April 15, 2003', '/(\w+) (\d+), (\d+)/i', '${1},$2,$3'], ['5/27/1999', '1999-5-27', '/(19|20)(\d{2})-(\d{1,2})-(\d{1,2})/', '\3/\4/\1\2'], ['Copyright 2000', 'Copyright 1999', '([0-9]+)', '2000'], - ['hello world! this is a test', 'HELLO WORLD! THIS is a test', '/\b([A-Z]+)\b/', function ($word) { - return strtolower($word[1]); - }], + ['hello world! this is a test', 'HELLO WORLD! THIS is a test', '/\b([A-Z]+)\b/', fn ($word) => strtolower($word[1])], ['COPYRIGHT 1999', 'Copyright 1999', '/[a-z]/', function ($matches) { foreach ($matches as $match) { return strtoupper($match); diff --git a/Tests/AbstractUnicodeTestCase.php b/Tests/AbstractUnicodeTestCase.php index d8f71ff..1ed16bc 100644 --- a/Tests/AbstractUnicodeTestCase.php +++ b/Tests/AbstractUnicodeTestCase.php @@ -44,9 +44,7 @@ public function testAscii() public function testAsciiClosureRule() { - $rule = function ($c) { - return str_replace('ö', 'OE', $c); - }; + $rule = fn ($c) => str_replace('ö', 'OE', $c); $s = static::createFromString('Dieser Wert sollte größer oder gleich'); $this->assertSame('Dieser Wert sollte grOEsser oder gleich', (string) $s->ascii([$rule])); diff --git a/Tests/LazyStringTest.php b/Tests/LazyStringTest.php index c311a3b..1c75402 100644 --- a/Tests/LazyStringTest.php +++ b/Tests/LazyStringTest.php @@ -29,6 +29,21 @@ public function testLazyString() $this->assertSame(1, $count); } + /** + * @runInSeparateProcess + */ + public function testReturnTypeError() + { + ErrorHandler::register(); + + $s = LazyString::fromCallable(fn () => []); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('Return value of '.__NAMESPACE__.'\{closure}() passed to '.LazyString::class.'::fromCallable() must be of the type string, array returned.'); + + (string) $s; + } + public function testLazyCallable() { $count = 0; @@ -55,21 +70,6 @@ public function __invoke() $this->assertSame(1, $count); } - /** - * @runInSeparateProcess - */ - public function testReturnTypeError() - { - ErrorHandler::register(); - - $s = LazyString::fromCallable(function () { return []; }); - - $this->expectException(\TypeError::class); - $this->expectExceptionMessage('Return value of '.__NAMESPACE__.'\{closure}() passed to '.LazyString::class.'::fromCallable() must be of the type string, array returned.'); - - (string) $s; - } - public function testFromStringable() { $this->assertInstanceOf(LazyString::class, LazyString::fromStringable('abc')); From 546b35724a3b69724c3959a39e18fa1ea9d7fd18 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 12 Feb 2023 23:57:18 +0100 Subject: [PATCH 36/68] Add void return types --- Slugger/AsciiSlugger.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index a573888..6e550c6 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -74,6 +74,9 @@ public function __construct(string $defaultLocale = null, array|\Closure $symbol $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } + /** + * @return void + */ public function setLocale(string $locale) { $this->defaultLocale = $locale; From d7459121033df7951b0a24b4850f2e5ffa0c0ac2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 14 Feb 2023 09:53:37 +0100 Subject: [PATCH 37/68] Fix merge --- Tests/Slugger/AsciiSluggerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Slugger/AsciiSluggerTest.php b/Tests/Slugger/AsciiSluggerTest.php index 163be0e..3544367 100644 --- a/Tests/Slugger/AsciiSluggerTest.php +++ b/Tests/Slugger/AsciiSluggerTest.php @@ -26,7 +26,7 @@ public function testSlug(string $expected, string $string, string $separator = ' $this->assertSame($expected, (string) $slugger->slug($string, $separator, $locale)); } - public function provideSlugTests(): iterable + public static function provideSlugTests(): iterable { yield ['', '']; yield ['foo', ' foo ']; @@ -60,7 +60,7 @@ public function testSlugEmoji(string $expected, string $string, ?string $locale, $this->assertSame($expected, (string) $slugger->slug($string, '-', $locale)); } - public function provideSlugEmojiTests(): iterable + public static function provideSlugEmojiTests(): iterable { yield [ 'un-chat-qui-sourit-chat-noir-et-un-tete-de-lion-vont-au-parc-national', From d5031e3631c5505da1e611fea440d5e0cecf856f Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 22 Feb 2023 21:14:08 +0100 Subject: [PATCH 38/68] [String] Remove unused private constant --- AbstractUnicodeString.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 8e97a72..ec1c78b 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -40,10 +40,6 @@ abstract class AbstractUnicodeString extends AbstractString private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ']; private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ']; - // the subset of upper case mappings that map one code point to many code points - private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ']; - private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂']; - // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆']; private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; From f2e190ee75ff0f5eced645ec0be5c66fac81f51f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 21 Mar 2023 22:03:43 +0100 Subject: [PATCH 39/68] Replace "use-by-ref" by static vars when possible in closures --- AbstractUnicodeString.php | 4 +++- LazyString.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index ec1c78b..d19a61a 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -155,7 +155,9 @@ public function ascii(array $rules = []): self public function camel(): static { $str = clone $this; - $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?![A-Z]{2,})/u', static function ($m) use (&$i) { + $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?![A-Z]{2,})/u', static function ($m) { + static $i = 0; + return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); diff --git a/LazyString.php b/LazyString.php index 9523b8c..3128ebb 100644 --- a/LazyString.php +++ b/LazyString.php @@ -30,7 +30,9 @@ public static function fromCallable(callable|array $callback, mixed ...$argument } $lazyString = new static(); - $lazyString->value = static function () use (&$callback, &$arguments, &$value): string { + $lazyString->value = static function () use (&$callback, &$arguments): string { + static $value; + if (null !== $arguments) { if (!\is_callable($callback)) { $callback[0] = $callback[0](); From 89bc6d5dcc94c89781e1f986e4d01b7ee91d684b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:38:00 +0200 Subject: [PATCH 40/68] [6.4] Allow 7.0 deps --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 3545c85..56c1368 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,11 @@ "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/intl": "^6.2", - "symfony/http-client": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "conflict": { "symfony/translation-contracts": "<2.5" From 92292507f95a4cca4b0e54cb4eba1be730c8cf7a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:24:39 +0200 Subject: [PATCH 41/68] [7.0] Bump to PHP 8.2 minimum --- LazyString.php | 2 +- composer.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/LazyString.php b/LazyString.php index 3128ebb..0341bea 100644 --- a/LazyString.php +++ b/LazyString.php @@ -129,7 +129,7 @@ private static function getPrettyName(callable $callback): string } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); - if (str_contains($r->name, '{closure}') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + if (str_contains($r->name, '{closure}') || !$class = $r->getClosureCalledClass()) { return $r->name; } diff --git a/composer.json b/composer.json index 56c1368..26ce26d 100644 --- a/composer.json +++ b/composer.json @@ -16,18 +16,18 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { "symfony/translation-contracts": "<2.5" From 4d37f9706681a1b98422f19394be9e13f196511e Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 1 Jul 2023 14:03:11 +0200 Subject: [PATCH 42/68] Add missing return types to magic methods --- UnicodeString.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/UnicodeString.php b/UnicodeString.php index a64c6a9..851e087 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -338,6 +338,9 @@ public function startsWith(string|iterable|AbstractString $prefix): bool return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); } + /** + * @return void + */ public function __wakeup() { if (!\is_string($this->string)) { From 09c2a386ca509a61348de71c40c844e42570a1d8 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 2 Jul 2023 23:52:21 +0200 Subject: [PATCH 43/68] [Components] Convert to native return types --- Slugger/AsciiSlugger.php | 5 +---- UnicodeString.php | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 6e550c6..9f7eba9 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -74,10 +74,7 @@ public function __construct(string $defaultLocale = null, array|\Closure $symbol $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } - /** - * @return void - */ - public function setLocale(string $locale) + public function setLocale(string $locale): void { $this->defaultLocale = $locale; } diff --git a/UnicodeString.php b/UnicodeString.php index 851e087..a888937 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -338,10 +338,7 @@ public function startsWith(string|iterable|AbstractString $prefix): bool return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); } - /** - * @return void - */ - public function __wakeup() + public function __wakeup(): void { if (!\is_string($this->string)) { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); From aa81d6dcc59657a1a96c3a9819239d2b84fd31a6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:28:24 +0200 Subject: [PATCH 44/68] Use typed properties in tests as much as possible --- Tests/LazyStringTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/LazyStringTest.php b/Tests/LazyStringTest.php index 1c75402..f2d24eb 100644 --- a/Tests/LazyStringTest.php +++ b/Tests/LazyStringTest.php @@ -49,7 +49,7 @@ public function testLazyCallable() $count = 0; $s = LazyString::fromCallable([function () use (&$count) { return new class($count) { - private $count; + private int $count; public function __construct(int &$count) { From 45d5e7c8e0bc666fc5cee99130319a7642087c33 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 18:41:43 +0200 Subject: [PATCH 45/68] Add types to private and internal properties --- AbstractUnicodeString.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index d19a61a..3284202 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -44,9 +44,9 @@ abstract class AbstractUnicodeString extends AbstractString private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆']; private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; - private static $transliterators = []; - private static $tableZero; - private static $tableWide; + private static array $transliterators = []; + private static array $tableZero; + private static array $tableWide; public static function fromCodePoints(int ...$codes): static { From fe9228ba417441e16f31d36ddad2b3076135f453 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 Jul 2023 17:12:55 +0200 Subject: [PATCH 46/68] More short closures + isset instead of null checks + etc. --- AbstractString.php | 4 ++-- AbstractUnicodeString.php | 4 ++-- ByteString.php | 6 +++--- UnicodeString.php | 44 ++++++++++++++++++++++++++++++--------- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index bf491f8..cc3a2e0 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -448,7 +448,7 @@ public function split(string $delimiter, int $limit = null, int $flags = null): $delimiter .= 'i'; } - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { @@ -507,7 +507,7 @@ public function toByteString(string $toEncoding = null): ByteString return $b; } - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { try { diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 3284202..df7265f 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -228,7 +228,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array $regexp .= 'i'; } - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { @@ -312,7 +312,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static $replace = 'preg_replace'; } - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) { diff --git a/ByteString.php b/ByteString.php index 212290f..f5050dd 100644 --- a/ByteString.php +++ b/ByteString.php @@ -236,7 +236,7 @@ public function match(string $regexp, int $flags = 0, int $offset = 0): array $regexp .= 'i'; } - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { @@ -300,7 +300,7 @@ public function replaceMatches(string $fromRegexp, string|callable $to): static $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { if (null === $string = $replace($fromRegexp, $to, $this->string)) { @@ -417,7 +417,7 @@ public function toCodePointString(string $fromEncoding = null): CodePointString return $u; } - set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); try { try { diff --git a/UnicodeString.php b/UnicodeString.php index 851e087..b33e37b 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -34,23 +34,32 @@ class UnicodeString extends AbstractUnicodeString { public function __construct(string $string = '') { - $this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string); + if ('' === $string || normalizer_is_normalized($this->string = $string)) { + return; + } - if (false === $this->string) { + if (false === $string = normalizer_normalize($string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } + + $this->string = $string; } public function append(string ...$suffix): static { $str = clone $this; $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); - normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); - if (false === $str->string) { + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } + $str->string = $string; + return $str; } @@ -209,12 +218,17 @@ public function prepend(string ...$prefix): static { $str = clone $this; $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; - normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); - if (false === $str->string) { + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } + $str->string = $string; + return $str; } @@ -235,11 +249,16 @@ public function replace(string $from, string $to): static } $str->string = $result.$tail; - normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); - if (false === $str->string) { + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } + + $str->string = $string; } return $str; @@ -269,12 +288,17 @@ public function splice(string $replacement, int $start = 0, int $length = null): $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); - normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); - if (false === $str->string) { + if (normalizer_is_normalized($str->string)) { + return $str; + } + + if (false === $string = normalizer_normalize($str->string)) { throw new InvalidArgumentException('Invalid UTF-8 string.'); } + $str->string = $string; + return $str; } From 95ff83d5a8a9f4b831cdd139e2ec10b931aa734f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:36:26 +0200 Subject: [PATCH 47/68] Add types to public and protected properties --- AbstractString.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index cc3a2e0..3f36eef 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -39,8 +39,8 @@ abstract class AbstractString implements \Stringable, \JsonSerializable public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE; public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE; - protected $string = ''; - protected $ignoreCase = false; + protected string $string = ''; + protected ?bool $ignoreCase = false; abstract public function __construct(string $string = ''); From afab7ffd61240b6fee63ea207b2b98c84f12791e Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 9 Nov 2023 09:50:06 +0100 Subject: [PATCH 48/68] remove error handler not needed on PHP 8 --- AbstractString.php | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index f0cd7bd..2190a29 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -507,23 +507,14 @@ public function toByteString(string $toEncoding = null): ByteString return $b; } - set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m)); - try { - try { - $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); - } catch (InvalidArgumentException|\ValueError $e) { - if (!\function_exists('iconv')) { - if ($e instanceof \ValueError) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } - throw $e; - } - - $b->string = iconv('UTF-8', $toEncoding, $this->string); + $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); + } catch (\ValueError $e) { + if (!\function_exists('iconv')) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } - } finally { - restore_error_handler(); + + $b->string = iconv('UTF-8', $toEncoding, $this->string); } return $b; From 2172dad98a2131a7a7a2ed01cfcea76c90a5d188 Mon Sep 17 00:00:00 2001 From: Bram Leeda Date: Fri, 20 Oct 2023 15:23:26 +0200 Subject: [PATCH 49/68] [String] New locale aware casing methods --- AbstractUnicodeString.php | 74 +++++++++++++++++++ CHANGELOG.md | 5 ++ Tests/AbstractUnicodeTestCase.php | 114 ++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index df7265f..af05329 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -220,6 +220,21 @@ public function lower(): static return $str; } + /** + * @param string $locale In the format language_region (e.g. tr_TR) + */ + public function localeLower(string $locale): static + { + if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Lower')) { + $str = clone $this; + $str->string = $transliterator->transliterate($str->string); + + return $str; + } + + return $this->lower(); + } + public function match(string $regexp, int $flags = 0, int $offset = 0): array { $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; @@ -363,6 +378,21 @@ public function title(bool $allWords = false): static return $str; } + /** + * @param string $locale In the format language_region (e.g. tr_TR) + */ + public function localeTitle(string $locale): static + { + if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Title')) { + $str = clone $this; + $str->string = $transliterator->transliterate($str->string); + + return $str; + } + + return $this->title(); + } + public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static { if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { @@ -450,6 +480,21 @@ public function upper(): static return $str; } + /** + * @param string $locale In the format language_region (e.g. tr_TR) + */ + public function localeUpper(string $locale): static + { + if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Upper')) { + $str = clone $this; + $str->string = $transliterator->transliterate($str->string); + + return $str; + } + + return $this->upper(); + } + public function width(bool $ignoreAnsiDecoration = true): int { $width = 0; @@ -587,4 +632,33 @@ private function wcswidth(string $string): int return $width; } + + private function getLocaleTransliterator(string $locale, string $id): ?\Transliterator + { + $rule = $locale.'-'.$id; + if (\array_key_exists($rule, self::$transliterators)) { + return self::$transliterators[$rule]; + } + + if (null !== $transliterator = self::$transliterators[$rule] = \Transliterator::create($rule)) { + return $transliterator; + } + + // Try to find a parent locale (nl_BE -> nl) + if (false === $i = strpos($locale, '_')) { + return null; + } + + $parentRule = substr_replace($locale, '-'.$id, $i); + + // Parent locale was already cached, return and store as current locale + if (\array_key_exists($parentRule, self::$transliterators)) { + return self::$transliterators[$rule] = self::$transliterators[$parentRule]; + } + + // Create transliterator based on parent locale and cache the result on both initial and parent locale values + $transliterator = \Transliterator::create($parentRule); + + return self::$transliterators[$rule] = self::$transliterators[$parentRule] = $transliterator; + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 31a3b54..621cedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `localeLower()`, `localeUpper()`, `localeTitle()` methods to `AbstractUnicodeString` + 6.2 --- diff --git a/Tests/AbstractUnicodeTestCase.php b/Tests/AbstractUnicodeTestCase.php index 1ed16bc..17461fc 100644 --- a/Tests/AbstractUnicodeTestCase.php +++ b/Tests/AbstractUnicodeTestCase.php @@ -50,6 +50,48 @@ public function testAsciiClosureRule() $this->assertSame('Dieser Wert sollte grOEsser oder gleich', (string) $s->ascii([$rule])); } + /** + * @dataProvider provideLocaleLower + * + * @requires extension intl + */ + public function testLocaleLower(string $locale, string $expected, string $origin) + { + $instance = static::createFromString($origin)->localeLower($locale); + + $this->assertNotSame(static::createFromString($origin), $instance); + $this->assertEquals(static::createFromString($expected), $instance); + $this->assertSame($expected, (string) $instance); + } + + /** + * @dataProvider provideLocaleUpper + * + * @requires extension intl + */ + public function testLocaleUpper(string $locale, string $expected, string $origin) + { + $instance = static::createFromString($origin)->localeUpper($locale); + + $this->assertNotSame(static::createFromString($origin), $instance); + $this->assertEquals(static::createFromString($expected), $instance); + $this->assertSame($expected, (string) $instance); + } + + /** + * @dataProvider provideLocaleTitle + * + * @requires extension intl + */ + public function testLocaleTitle(string $locale, string $expected, string $origin) + { + $instance = static::createFromString($origin)->localeTitle($locale); + + $this->assertNotSame(static::createFromString($origin), $instance); + $this->assertEquals(static::createFromString($expected), $instance); + $this->assertSame($expected, (string) $instance); + } + public function provideCreateFromCodePoint(): array { return [ @@ -291,6 +333,78 @@ public static function provideLower(): array ); } + public static function provideLocaleLower(): array + { + return [ + // Lithuanian + // Introduce an explicit dot above when lowercasing capital I's and J's + // whenever there are more accents above. + // LATIN CAPITAL LETTER I WITH OGONEK -> LATIN SMALL LETTER I WITH OGONEK + ['lt', 'į', 'Į'], + // LATIN CAPITAL LETTER I WITH GRAVE -> LATIN SMALL LETTER I COMBINING DOT ABOVE + ['lt', 'i̇̀', 'Ì'], + // LATIN CAPITAL LETTER I WITH ACUTE -> LATIN SMALL LETTER I COMBINING DOT ABOVE COMBINING ACUTE ACCENT + ['lt', 'i̇́', 'Í'], + // LATIN CAPITAL LETTER I WITH TILDE -> LATIN SMALL LETTER I COMBINING DOT ABOVE COMBINING TILDE + ['lt', 'i̇̃', 'Ĩ'], + + // Turkish and Azeri + // When lowercasing, remove dot_above in the sequence I + dot_above, which will turn into 'i'. + // LATIN CAPITAL LETTER I WITH DOT ABOVE -> LATIN SMALL LETTER I + ['tr', 'i', 'İ'], + ['tr_TR', 'i', 'İ'], + ['az', 'i', 'İ'], + + // Default casing rules + // LATIN CAPITAL LETTER I WITH DOT ABOVE -> LATIN SMALL LETTER I COMBINING DOT ABOVE + ['en_US', 'i̇', 'İ'], + ['en', 'i̇', 'İ'], + ]; + } + + public static function provideLocaleUpper(): array + { + return [ + // Turkish and Azeri + // When uppercasing, i turns into a dotted capital I + // LATIN SMALL LETTER I -> LATIN CAPITAL LETTER I WITH DOT ABOVE + ['tr', 'İ', 'i'], + ['tr_TR', 'İ', 'i'], + ['az', 'İ', 'i'], + + // Greek + // Remove accents when uppercasing + // GREEK SMALL LETTER ALPHA WITH TONOS -> GREEK CAPITAL LETTER ALPHA + ['el', 'Α', 'ά'], + ['el_GR', 'Α', 'ά'], + + // Default casing rules + // GREEK SMALL LETTER ALPHA WITH TONOS -> GREEK CAPITAL LETTER ALPHA WITH TONOS + ['en_US', 'Ά', 'ά'], + ['en', 'Ά', 'ά'], + ]; + } + + public static function provideLocaleTitle(): array + { + return [ + // Greek + // Titlecasing words, should keep the accents on the first letter + ['el', 'Άδικος', 'άδικος'], + ['el_GR', 'Άδικος', 'άδικος'], + ['en', 'Άδικος', 'άδικος'], + + // Dutch + // Title casing should treat 'ij' as one character + ['nl_NL', 'IJssel', 'ijssel'], + ['nl_BE', 'IJssel', 'ijssel'], + ['nl', 'IJssel', 'ijssel'], + + // Default casing rules + ['en', 'Ijssel', 'ijssel'], + ]; + } + public static function provideUpper(): array { return array_merge( From 1cb3ad53b50464c7158268eee17b4e7fc742aec6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 30 Dec 2023 20:21:21 +0100 Subject: [PATCH 50/68] Leverage ReflectionFunction::isAnonymous() --- LazyString.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyString.php b/LazyString.php index 0341bea..4a303e1 100644 --- a/LazyString.php +++ b/LazyString.php @@ -129,7 +129,7 @@ private static function getPrettyName(callable $callback): string } elseif ($callback instanceof \Closure) { $r = new \ReflectionFunction($callback); - if (str_contains($r->name, '{closure}') || !$class = $r->getClosureCalledClass()) { + if ($r->isAnonymous() || !$class = $r->getClosureCalledClass()) { return $r->name; } From 7a06dfa63d80e98eedc362c12fad3381a249cd63 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 29 Dec 2023 21:35:44 +0100 Subject: [PATCH 51/68] [String] Use CPP --- Resources/WcswidthDataGenerator.php | 8 +++----- Slugger/AsciiSlugger.php | 8 ++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Resources/WcswidthDataGenerator.php b/Resources/WcswidthDataGenerator.php index 6425ecc..dd6b923 100644 --- a/Resources/WcswidthDataGenerator.php +++ b/Resources/WcswidthDataGenerator.php @@ -21,13 +21,11 @@ */ final class WcswidthDataGenerator { - private string $outDir; private HttpClientInterface $client; - public function __construct(string $outDir) - { - $this->outDir = $outDir; - + public function __construct( + private string $outDir, + ) { $this->client = HttpClient::createForBaseUri('https://www.unicode.org/Public/UNIDATA/'); } diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 9f7eba9..4f428da 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -55,7 +55,6 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface 'zh' => 'Han-Latin', ]; - private ?string $defaultLocale; private \Closure|array $symbolsMap = [ 'en' => ['@' => 'at', '&' => 'and'], ]; @@ -68,9 +67,10 @@ class AsciiSlugger implements SluggerInterface, LocaleAwareInterface */ private array $transliterators = []; - public function __construct(string $defaultLocale = null, array|\Closure $symbolsMap = null) - { - $this->defaultLocale = $defaultLocale; + public function __construct( + private ?string $defaultLocale = null, + array|\Closure $symbolsMap = null, + ) { $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; } From dd989887f0b906f5245888eab8fa0cc558382974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 16 Dec 2023 08:55:09 +0100 Subject: [PATCH 52/68] Move & adapt "emoji code" from Intl into its own component Transfert emoji data from Intl to emoji component Update main composer.json Fix phpunit config Update composer and README descriptions Fix LICENCE date Update src/Symfony/Component/Intl/CHANGELOG.md Co-authored-by: Oskar Stark Fix Changelog I feel that cool resolve some of the recent issues linked to the Profiler. Rename component Emoji + unlink from Intl (no shared resp/code) Isolated commit to move data Update Github worflows Use Emoji in String component Update src/Symfony/Component/Emoji/CHANGELOG.md Co-authored-by: Nicolas Grekas Update src/Symfony/Component/Emoji/README.md Co-authored-by: Nicolas Grekas Present the compress command in both README's Update src/Symfony/Component/Intl/CHANGELOG.md Co-authored-by: Nicolas Grekas Fix main composer.json Revert symfony/intl requires symfony/emoji Remove EmojiTransliteratorTrait Move emoji data Add "symfony/deprecation-contracts" to Intl Revert data test split Add symfony/emoji to String (dev) Fix String Test namespace Fix .gitattributes hides "bin/compress" script Please Psalm ? Compute quickCheck once Update LICENCE Add Intl conflict with string < 7.1 Fix Int changelog Fix composer.json CS Throw exception in Intl BC layer when symfony/emoji is not installed Test Intl & Emoji in the same job Remove useless check Remove useless check (without breaking things) --- Slugger/AsciiSlugger.php | 4 ++-- Tests/Slugger/AsciiSluggerTest.php | 2 +- composer.json | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index 4f428da..c937318 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -11,7 +11,7 @@ namespace Symfony\Component\String\Slugger; -use Symfony\Component\Intl\Transliterator\EmojiTransliterator; +use Symfony\Component\Emoji\EmojiTransliterator; use Symfony\Component\String\AbstractUnicodeString; use Symfony\Component\String\UnicodeString; use Symfony\Contracts\Translation\LocaleAwareInterface; @@ -92,7 +92,7 @@ public function getLocale(): string public function withEmoji(bool|string $emoji = true): static { if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { - throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__)); + throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/emoji" package is not installed. Try running "composer require symfony/emoji".', __METHOD__)); } $new = clone $this; diff --git a/Tests/Slugger/AsciiSluggerTest.php b/Tests/Slugger/AsciiSluggerTest.php index 3544367..ab4b799 100644 --- a/Tests/Slugger/AsciiSluggerTest.php +++ b/Tests/Slugger/AsciiSluggerTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\String; +namespace Symfony\Component\String\Tests\Slugger; use PHPUnit\Framework\TestCase; use Symfony\Component\String\Slugger\AsciiSlugger; diff --git a/composer.json b/composer.json index 26ce26d..1959f5d 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,9 @@ }, "require-dev": { "symfony/error-handler": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/emoji": "^7.0", "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^6.4|^7.0" }, From 7a14736fb179876575464e4658fce0c304e8c15b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 24 Jan 2024 19:53:26 +0100 Subject: [PATCH 53/68] [DependencyInjection] Fix loading all env vars from secrets when only a subset is needed --- LazyString.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyString.php b/LazyString.php index 3128ebb..1af3769 100644 --- a/LazyString.php +++ b/LazyString.php @@ -39,7 +39,7 @@ public static function fromCallable(callable|array $callback, mixed ...$argument $callback[1] ??= '__invoke'; } $value = $callback(...$arguments); - $callback = self::getPrettyName($callback); + $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable'; $arguments = null; } From 3d0a98879bbc7585aff5f9a5a539074328cc2ca0 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 3 Feb 2024 20:41:36 +0100 Subject: [PATCH 54/68] bump version for symfony/emoji --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1959f5d..10d0ee6 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ }, "require-dev": { "symfony/error-handler": "^6.4|^7.0", - "symfony/emoji": "^7.0", + "symfony/emoji": "^7.1", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", From ef964850371aca5def9fc8a3059d9b1690bb3b6c Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 8 Mar 2024 13:47:07 +0100 Subject: [PATCH 55/68] [String] Leverage Randomizer::getBytesFromString() --- ByteString.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ByteString.php b/ByteString.php index 3ebe43c..e6b56ae 100644 --- a/ByteString.php +++ b/ByteString.php @@ -11,6 +11,7 @@ namespace Symfony\Component\String; +use Random\Randomizer; use Symfony\Component\String\Exception\ExceptionInterface; use Symfony\Component\String\Exception\InvalidArgumentException; use Symfony\Component\String\Exception\RuntimeException; @@ -55,6 +56,10 @@ public static function fromRandom(int $length = 16, ?string $alphabet = null): s throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); } + if (\PHP_VERSION_ID >= 80300) { + return new static((new Randomizer())->getBytesFromString($alphabet, $length)); + } + $ret = ''; while ($length > 0) { $urandomLength = (int) ceil(2 * $length * $bits / 8.0); From 29dc30bf66fe1a2b70c31543012bc108a81dc3c5 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 18 Mar 2024 20:27:13 +0100 Subject: [PATCH 56/68] chore: CS fixes --- Resources/bin/update-data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/bin/update-data.php b/Resources/bin/update-data.php index c0bd1c1..2ba0cd8 100644 --- a/Resources/bin/update-data.php +++ b/Resources/bin/update-data.php @@ -18,7 +18,7 @@ error_reporting(\E_ALL); set_error_handler(static function (int $type, string $msg, string $file, int $line): void { - throw new \ErrorException($msg, 0, $type, $file, $line); + throw new ErrorException($msg, 0, $type, $file, $line); }); set_exception_handler(static function (Throwable $exception): void { From adfa98a608104d8f6b4d008d144523f8b79f0016 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 57/68] Prefix all sprintf() calls --- AbstractString.php | 12 ++++++------ AbstractUnicodeString.php | 4 ++-- ByteString.php | 4 ++-- LazyString.php | 4 ++-- Resources/WcswidthDataGenerator.php | 2 +- Slugger/AsciiSlugger.php | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/AbstractString.php b/AbstractString.php index 253d2dc..d68f334 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -263,7 +263,7 @@ public function containsAny(string|iterable $needle): bool public function endsWith(string|iterable $suffix): bool { if (\is_string($suffix)) { - throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($suffix as $s) { @@ -312,7 +312,7 @@ public function ensureStart(string $prefix): static public function equalsTo(string|iterable $string): bool { if (\is_string($string)) { - throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($string as $s) { @@ -340,7 +340,7 @@ public function ignoreCase(): static public function indexOf(string|iterable $needle, int $offset = 0): ?int { if (\is_string($needle)) { - throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } $i = \PHP_INT_MAX; @@ -362,7 +362,7 @@ public function indexOf(string|iterable $needle, int $offset = 0): ?int public function indexOfLast(string|iterable $needle, int $offset = 0): ?int { if (\is_string($needle)) { - throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } $i = null; @@ -414,7 +414,7 @@ abstract public function prepend(string ...$prefix): static; public function repeat(int $multiplier): static { if (0 > $multiplier) { - throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); + throw new InvalidArgumentException(\sprintf('Multiplier must be positive, %d given.', $multiplier)); } $str = clone $this; @@ -481,7 +481,7 @@ public function split(string $delimiter, ?int $limit = null, ?int $flags = null) public function startsWith(string|iterable $prefix): bool { if (\is_string($prefix)) { - throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + throw new \TypeError(\sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); } foreach ($prefix as $prefix) { diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index f3acb2e..45649ff 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -124,7 +124,7 @@ public function ascii(array $rules = []): self } if (null === $transliterator) { - throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule)); + throw new InvalidArgumentException(\sprintf('Unknown transliteration rule "%s".', $rule)); } self::$transliterators['any-latin/bgn'] = $transliterator; @@ -139,7 +139,7 @@ public function ascii(array $rules = []): self $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { - throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); + throw new \LogicException(\sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); } return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); diff --git a/ByteString.php b/ByteString.php index e6b56ae..98bad02 100644 --- a/ByteString.php +++ b/ByteString.php @@ -46,7 +46,7 @@ public function __construct(string $string = '') public static function fromRandom(int $length = 16, ?string $alphabet = null): self { if ($length <= 0) { - throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); + throw new InvalidArgumentException(\sprintf('A strictly positive length is expected, "%d" given.', $length)); } $alphabet ??= self::ALPHABET_ALPHANUMERIC; @@ -441,7 +441,7 @@ public function toCodePointString(?string $fromEncoding = null): CodePointString } if (!$validEncoding) { - throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); + throw new InvalidArgumentException(\sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); } $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); diff --git a/LazyString.php b/LazyString.php index 8f2bbbf..b86d733 100644 --- a/LazyString.php +++ b/LazyString.php @@ -26,7 +26,7 @@ class LazyString implements \Stringable, \JsonSerializable public static function fromCallable(callable|array $callback, mixed ...$arguments): static { if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) instanceof \Closure || 2 < \count($callback))) { - throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']')); + throw new \TypeError(\sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']')); } $lazyString = new static(); @@ -94,7 +94,7 @@ public function __toString(): string $r = new \ReflectionFunction($this->value); $callback = $r->getStaticVariables()['callback']; - $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); + $e = new \TypeError(\sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); } throw $e; diff --git a/Resources/WcswidthDataGenerator.php b/Resources/WcswidthDataGenerator.php index dd6b923..19e6e89 100644 --- a/Resources/WcswidthDataGenerator.php +++ b/Resources/WcswidthDataGenerator.php @@ -71,7 +71,7 @@ private function write(string $fileName, string $version, array $rawData): void $content = $this->getHeader($version).'return '.VarExporter::export($this->format($rawData)).";\n"; if (!file_put_contents($this->outDir.'/'.$fileName, $content)) { - throw new RuntimeException(sprintf('The "%s" file could not be written.', $fileName)); + throw new RuntimeException(\sprintf('The "%s" file could not be written.', $fileName)); } } diff --git a/Slugger/AsciiSlugger.php b/Slugger/AsciiSlugger.php index d254532..9d4edf1 100644 --- a/Slugger/AsciiSlugger.php +++ b/Slugger/AsciiSlugger.php @@ -92,7 +92,7 @@ public function getLocale(): string public function withEmoji(bool|string $emoji = true): static { if (false !== $emoji && !class_exists(EmojiTransliterator::class)) { - throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/emoji" package is not installed. Try running "composer require symfony/emoji".', __METHOD__)); + throw new \LogicException(\sprintf('You cannot use the "%s()" method as the "symfony/emoji" package is not installed. Try running "composer require symfony/emoji".', __METHOD__)); } $new = clone $this; From 531bcd7d236925c5e4ee52fb5b272f9277aef872 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 58/68] chore: CS fixes --- Inflector/EnglishInflector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Inflector/EnglishInflector.php b/Inflector/EnglishInflector.php index 56f03b7..9c1b9cb 100644 --- a/Inflector/EnglishInflector.php +++ b/Inflector/EnglishInflector.php @@ -141,7 +141,7 @@ final class EnglishInflector implements InflectorInterface // shoes (shoe) ['se', 2, true, true, ['', 'e']], - // status (status) + // status (status) ['sutats', 6, true, true, 'status'], // tags (tag) From 25ad779fed5c907f820454d981f26b2f945a14ab Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Thu, 30 May 2024 14:00:21 +0200 Subject: [PATCH 59/68] [String] Add WORD_STRICT mode to truncate method --- AbstractString.php | 13 ++++++++-- CHANGELOG.md | 5 ++++ Tests/AbstractAsciiTestCase.php | 15 ++++++++++-- TruncateMode.php | 42 +++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 TruncateMode.php diff --git a/AbstractString.php b/AbstractString.php index d68f334..176a7ae 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -605,7 +605,7 @@ public function trimSuffix($suffix): static return $str; } - public function truncate(int $length, string $ellipsis = '', bool $cut = true): static + public function truncate(int $length, string $ellipsis = '', bool|TruncateMode $cut = TruncateMode::Char): static { $stringLength = $this->length(); @@ -619,7 +619,8 @@ public function truncate(int $length, string $ellipsis = '', bool $cut = true): $ellipsisLength = 0; } - if (!$cut) { + $desiredLength = $length; + if (TruncateMode::WordAfter === $cut || TruncateMode::WordBefore === $cut || !$cut) { if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { return clone $this; } @@ -629,6 +630,14 @@ public function truncate(int $length, string $ellipsis = '', bool $cut = true): $str = $this->slice(0, $length - $ellipsisLength); + if (TruncateMode::WordBefore === $cut) { + if (0 === $ellipsisLength && $desiredLength === $this->indexOf([' ', "\r", "\n", "\t"], $length)) { + return $str; + } + + $str = $str->beforeLast([' ', "\r", "\n", "\t"]); + } + return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; } diff --git a/CHANGELOG.md b/CHANGELOG.md index 621cedf..f1f7204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Add `TruncateMode` enum to handle more truncate methods + 7.1 --- diff --git a/Tests/AbstractAsciiTestCase.php b/Tests/AbstractAsciiTestCase.php index f8b0509..3cbcd08 100644 --- a/Tests/AbstractAsciiTestCase.php +++ b/Tests/AbstractAsciiTestCase.php @@ -16,6 +16,7 @@ use Symfony\Component\String\ByteString; use Symfony\Component\String\CodePointString; use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\TruncateMode; use Symfony\Component\String\UnicodeString; abstract class AbstractAsciiTestCase extends TestCase @@ -1500,22 +1501,24 @@ public static function providePadStart() /** * @dataProvider provideTruncate */ - public function testTruncate(string $expected, string $origin, int $length, string $ellipsis, bool $cut = true) + public function testTruncate(string $expected, string $origin, int $length, string $ellipsis, bool|TruncateMode $cut = TruncateMode::Char) { $instance = static::createFromString($origin)->truncate($length, $ellipsis, $cut); $this->assertEquals(static::createFromString($expected), $instance); } - public static function provideTruncate() + public static function provideTruncate(): array { return [ ['', '', 3, ''], ['', 'foo', 0, '...'], ['foo', 'foo', 0, '...', false], + ['foo', 'foo', 0, '...', TruncateMode::WordAfter], ['fo', 'foobar', 2, ''], ['foobar', 'foobar', 10, ''], ['foobar', 'foobar', 10, '...', false], + ['foobar', 'foobar', 10, '...', TruncateMode::WordAfter], ['foo', 'foo', 3, '...'], ['fo', 'foobar', 2, '...'], ['...', 'foobar', 3, '...'], @@ -1524,6 +1527,14 @@ public static function provideTruncate() ['foobar...', 'foobar foo', 7, '...', false], ['foobar foo...', 'foobar foo a', 10, '...', false], ['foobar foo aar', 'foobar foo aar', 12, '...', false], + ['foobar...', 'foobar foo', 6, '...', TruncateMode::WordAfter], + ['foobar...', 'foobar foo', 7, '...', TruncateMode::WordAfter], + ['foobar foo...', 'foobar foo a', 10, '...', TruncateMode::WordAfter], + ['foobar foo aar', 'foobar foo aar', 12, '...', TruncateMode::WordAfter], + ['foobar foo', 'foobar foo aar', 10, '', TruncateMode::WordBefore], + ['foobar...', 'foobar foo aar', 10, '...', TruncateMode::WordBefore], + ['Lorem ipsum', 'Lorem ipsum dolor sit amet', 14, '', TruncateMode::WordBefore], + ['Lorem...', 'Lorem ipsum dolor sit amet', 10, '...', TruncateMode::WordBefore], ]; } diff --git a/TruncateMode.php b/TruncateMode.php new file mode 100644 index 0000000..12568cd --- /dev/null +++ b/TruncateMode.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +enum TruncateMode +{ + /** + * Will cut exactly at given length. + * + * Length: 14 + * Source: Lorem ipsum dolor sit amet + * Output: Lorem ipsum do + */ + case Char; + + /** + * Returns the string up to the last complete word containing the specified length. + * + * Length: 14 + * Source: Lorem ipsum dolor sit amet + * Output: Lorem ipsum + */ + case WordBefore; + + /** + * Returns the string up to the complete word after or at the given length. + * + * Length: 14 + * Source: Lorem ipsum dolor sit amet + * Output: Lorem ipsum dolor + */ + case WordAfter; +} From 10e1cbfff6d8f2c14f8f6236d5a1d9bca48797e8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 60/68] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0f57d86..166549d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,5 +2,4 @@ /Resources/WcswidthDataGenerator.php export-ignore /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From f9fde996ab74dcc429d00161da2fad36a1f47d13 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 19 Jul 2024 14:33:38 +0200 Subject: [PATCH 61/68] fix truncating in WordBefore mode with length after last space --- AbstractString.php | 4 +++- Tests/AbstractAsciiTestCase.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/AbstractString.php b/AbstractString.php index 176a7ae..81636c4 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -620,11 +620,13 @@ public function truncate(int $length, string $ellipsis = '', bool|TruncateMode $ } $desiredLength = $length; - if (TruncateMode::WordAfter === $cut || TruncateMode::WordBefore === $cut || !$cut) { + if (TruncateMode::WordAfter === $cut || !$cut) { if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { return clone $this; } + $length += $ellipsisLength; + } elseif (TruncateMode::WordBefore === $cut && null !== $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { $length += $ellipsisLength; } diff --git a/Tests/AbstractAsciiTestCase.php b/Tests/AbstractAsciiTestCase.php index 0c04f30..196b55a 100644 --- a/Tests/AbstractAsciiTestCase.php +++ b/Tests/AbstractAsciiTestCase.php @@ -1534,14 +1534,42 @@ public static function provideTruncate(): array ['foobar...', 'foobar foo', 7, '...', false], ['foobar foo...', 'foobar foo a', 10, '...', false], ['foobar foo aar', 'foobar foo aar', 12, '...', false], + ['foobar', 'foobar foo', 6, '', TruncateMode::Char], + ['foobar', 'foobar foo', 6, '', TruncateMode::WordAfter], + ['foobar', 'foobar foo', 6, '', TruncateMode::WordBefore], + ['foo...', 'foobar foo', 6, '...', TruncateMode::Char], ['foobar...', 'foobar foo', 6, '...', TruncateMode::WordAfter], + ['foobar...', 'foobar foo', 6, '...', TruncateMode::WordBefore], + ['foobar ', 'foobar foo', 7, '', TruncateMode::Char], + ['foobar', 'foobar foo', 7, '', TruncateMode::WordAfter], + ['foobar', 'foobar foo', 7, '', TruncateMode::WordBefore], + ['foob...', 'foobar foo', 7, '...', TruncateMode::Char], ['foobar...', 'foobar foo', 7, '...', TruncateMode::WordAfter], + ['foobar...', 'foobar foo', 7, '...', TruncateMode::WordBefore], + ['foobar foo', 'foobar foo a', 10, '', TruncateMode::Char], + ['foobar foo', 'foobar foo a', 10, '', TruncateMode::WordAfter], + ['foobar foo', 'foobar foo a', 10, '', TruncateMode::WordBefore], + ['foobar...', 'foobar foo a', 10, '...', TruncateMode::Char], ['foobar foo...', 'foobar foo a', 10, '...', TruncateMode::WordAfter], + ['foobar...', 'foobar foo a', 10, '...', TruncateMode::WordBefore], + ['foobar foo a', 'foobar foo aar', 12, '', TruncateMode::Char], + ['foobar foo aar', 'foobar foo aar', 12, '', TruncateMode::WordAfter], + ['foobar foo', 'foobar foo aar', 12, '', TruncateMode::WordBefore], + ['foobar fo...', 'foobar foo aar', 12, '...', TruncateMode::Char], ['foobar foo aar', 'foobar foo aar', 12, '...', TruncateMode::WordAfter], + ['foobar...', 'foobar foo aar', 12, '...', TruncateMode::WordBefore], + ['foobar foo', 'foobar foo aar', 10, '', TruncateMode::Char], ['foobar foo', 'foobar foo aar', 10, '', TruncateMode::WordBefore], + ['foobar foo', 'foobar foo aar', 10, '', TruncateMode::WordAfter], + ['foobar...', 'foobar foo aar', 10, '...', TruncateMode::Char], ['foobar...', 'foobar foo aar', 10, '...', TruncateMode::WordBefore], + ['foobar foo...', 'foobar foo aar', 10, '...', TruncateMode::WordAfter], + ['Lorem ipsum do', 'Lorem ipsum dolor sit amet', 14, '', TruncateMode::Char], ['Lorem ipsum', 'Lorem ipsum dolor sit amet', 14, '', TruncateMode::WordBefore], + ['Lorem ipsum dolor', 'Lorem ipsum dolor sit amet', 14, '', TruncateMode::WordAfter], + ['Lorem i...', 'Lorem ipsum dolor sit amet', 10, '...', TruncateMode::Char], ['Lorem...', 'Lorem ipsum dolor sit amet', 10, '...', TruncateMode::WordBefore], + ['Lorem ipsum...', 'Lorem ipsum dolor sit amet', 10, '...', TruncateMode::WordAfter], ]; } From 37188300067dda3c2711b870e03a523d1b3e7db1 Mon Sep 17 00:00:00 2001 From: Thomas Durand Date: Mon, 29 Jul 2024 18:11:56 +0200 Subject: [PATCH 62/68] [String] Fixed Quorum plural, that was inflected to be only "Quora" and never "Quorums" --- Inflector/EnglishInflector.php | 6 ++++++ Tests/Inflector/EnglishInflectorTest.php | 3 +++ 2 files changed, 9 insertions(+) diff --git a/Inflector/EnglishInflector.php b/Inflector/EnglishInflector.php index 77ebc13..dee05be 100644 --- a/Inflector/EnglishInflector.php +++ b/Inflector/EnglishInflector.php @@ -37,6 +37,9 @@ final class EnglishInflector implements InflectorInterface // curricula (curriculum) ['alucirruc', 9, true, true, 'curriculum'], + // quora (quorum) + ['arouq', 5, true, true, 'quorum'], + // genera (genus) ['areneg', 6, true, true, 'genus'], @@ -265,6 +268,9 @@ final class EnglishInflector implements InflectorInterface // albums (album) ['mubla', 5, true, true, 'albums'], + // quorums (quorum) + ['murouq', 6, true, true, ['quora', 'quorums']], + // bacteria (bacterium), curricula (curriculum), media (medium), memoranda (memorandum), phenomena (phenomenon), strata (stratum) ['mu', 2, true, true, 'a'], diff --git a/Tests/Inflector/EnglishInflectorTest.php b/Tests/Inflector/EnglishInflectorTest.php index fb5d043..cfe8d37 100644 --- a/Tests/Inflector/EnglishInflectorTest.php +++ b/Tests/Inflector/EnglishInflectorTest.php @@ -134,6 +134,8 @@ public static function singularizeProvider() ['poppies', 'poppy'], ['prices', ['prex', 'prix', 'price']], ['quizzes', 'quiz'], + ['quora', 'quorum'], + ['quorums', 'quorum'], ['radii', 'radius'], ['roofs', 'roof'], ['roses', ['ros', 'rose', 'rosis']], @@ -285,6 +287,7 @@ public static function pluralizeProvider() ['poppy', 'poppies'], ['price', 'prices'], ['quiz', 'quizzes'], + ['quorum', ['quora', 'quorums']], ['radius', 'radii'], ['roof', ['roofs', 'rooves']], ['rose', 'roses'], From a97458d764c940e73b5debf56fe2054b9423eab4 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 29 Jul 2024 09:33:48 +0200 Subject: [PATCH 63/68] Remove useless code --- ByteString.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByteString.php b/ByteString.php index 98bad02..5cbfd6d 100644 --- a/ByteString.php +++ b/ByteString.php @@ -340,7 +340,7 @@ public function reverse(): static public function slice(int $start = 0, ?int $length = null): static { $str = clone $this; - $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); + $str->string = substr($this->string, $start, $length ?? \PHP_INT_MAX); return $str; } From f1f4d05da68b136a32593bf967cc716f94b2d95b Mon Sep 17 00:00:00 2001 From: Dennis Tobar Date: Tue, 10 Sep 2024 22:55:42 -0300 Subject: [PATCH 64/68] [String] Add Spanish inflector with some rules --- Inflector/SpanishInflector.php | 126 ++++++++++++++++++ Tests/Inflector/SpanishInflectorTest.php | 158 +++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 Inflector/SpanishInflector.php create mode 100644 Tests/Inflector/SpanishInflectorTest.php diff --git a/Inflector/SpanishInflector.php b/Inflector/SpanishInflector.php new file mode 100644 index 0000000..4b98cb6 --- /dev/null +++ b/Inflector/SpanishInflector.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +final class SpanishInflector implements InflectorInterface +{ + /** + * A list of all rules for pluralise. + * + * @see https://www.spanishdict.com/guide/spanish-plural-noun-forms + * @see https://www.rae.es/gram%C3%A1tica/morfolog%C3%ADa/la-formaci%C3%B3n-del-plural-plurales-en-s-y-plurales-en-es-reglas-generales + */ + // First entry: regex + // Second entry: replacement + private const PLURALIZE_REGEXP = [ + // Specials sí, no + ['/(sí|no)$/i', '\1es'], + + // Words ending with vowel must use -s (RAE 3.2a, 3.2c) + ['/(a|e|i|o|u|á|é|í|ó|ú)$/i', '\1s'], + + // Word ending in s or x and the previous letter is accented (RAE 3.2n) + ['/ás$/i', 'ases'], + ['/és$/i', 'eses'], + ['/ís$/i', 'ises'], + ['/ós$/i', 'oses'], + ['/ús$/i', 'uses'], + + // Words ending in -ión must changed to -iones + ['/ión$/i', '\1iones'], + + // Words ending in some consonants must use -es (RAE 3.2k) + ['/(l|r|n|d|j|s|x|ch|y)$/i', '\1es'], + + // Word ending in z, must changed to ces + ['/(z)$/i', 'ces'], + ]; + + /** + * A list of all rules for singularize. + */ + private const SINGULARIZE_REGEXP = [ + // Specials sí, no + ['/(sí|no)es$/i', '\1'], + + // Words ending in -ión must changed to -iones + ['/iones$/i', '\1ión'], + + // Word ending in z, must changed to ces + ['/ces$/i', 'z'], + + // Word ending in s or x and the previous letter is accented (RAE 3.2n) + ['/(\w)ases$/i', '\1ás'], + ['/eses$/i', 'és'], + ['/ises$/i', 'ís'], + ['/(\w{2,})oses$/i', '\1ós'], + ['/(\w)uses$/i', '\1ús'], + + // Words ending in some consonants and -es, must be the consonants + ['/(l|r|n|d|j|s|x|ch|y)e?s$/i', '\1'], + + // Words ended with vowel and s, must be vowel + ['/(a|e|i|o|u|á|é|ó|í|ú)s$/i', '\1'], + ]; + + private const UNINFLECTED_RULES = [ + // Words ending with pies (RAE 3.2n) + '/.*(piés)$/i', + ]; + + private const UNINFLECTED = '/^(lunes|martes|miércoles|jueves|viernes|análisis|torax|yo|pies)$/i'; + + public function singularize(string $plural): array + { + if ($this->isInflectedWord($plural)) { + return [$plural]; + } + + foreach (self::SINGULARIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $plural)) { + return [preg_replace($regexp, $replace, $plural)]; + } + } + + return [$plural]; + } + + public function pluralize(string $singular): array + { + if ($this->isInflectedWord($singular)) { + return [$singular]; + } + + foreach (self::PLURALIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $singular)) { + return [preg_replace($regexp, $replace, $singular)]; + } + } + + return [$singular.'s']; + } + + private function isInflectedWord(string $word): bool + { + foreach (self::UNINFLECTED_RULES as $rule) { + if (1 === preg_match($rule, $word)) { + return true; + } + } + + return 1 === preg_match(self::UNINFLECTED, $word); + } +} diff --git a/Tests/Inflector/SpanishInflectorTest.php b/Tests/Inflector/SpanishInflectorTest.php new file mode 100644 index 0000000..f0b8e42 --- /dev/null +++ b/Tests/Inflector/SpanishInflectorTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Tests\Inflector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\String\Inflector\SpanishInflector; + +class SpanishInflectorTest extends TestCase +{ + public static function singularizeProvider(): array + { + return [ + // vowels (RAE 3.2a, 3.2c) + ['peras', 'pera'], + ['especies', 'especie'], + ['álcalis', 'álcali'], + ['códigos', 'código'], + ['espíritus', 'espíritu'], + + // accented (RAE 3.2a, 3.2c) + ['papás', 'papá'], + ['cafés', 'café'], + ['isrealís', 'isrealí'], + ['burós', 'buró'], + ['tisús', 'tisú'], + + // ending in -ión + ['aviones', 'avión'], + ['camiones', 'camión'], + + // ending in some letters (RAE 3.2k) + ['amores', 'amor'], + ['antifaces', 'antifaz'], + ['atriles', 'atril'], + ['fácsimiles', 'fácsimil'], + ['vides', 'vid'], + ['reyes', 'rey'], + ['relojes', 'reloj'], + ['faxes', 'fax'], + ['sándwiches', 'sándwich'], + ['cánones', 'cánon'], + + // (RAE 3.2n) + ['adioses', 'adiós'], + ['aguarrases', 'aguarrás'], + ['arneses', 'arnés'], + ['autobuses', 'autobús'], + ['kermeses', 'kermés'], + ['palmareses', 'palmarés'], + ['toses', 'tos'], + + // Special + ['síes', 'sí'], + ['noes', 'no'], + ]; + } + + public static function pluralizeProvider(): array + { + return [ + // vowels (RAE 3.2a, 3.2c) + ['pera', 'peras'], + ['especie', 'especies'], + ['álcali', 'álcalis'], + ['código', 'códigos'], + ['espíritu', 'espíritus'], + + // accented (RAE 3.2a, 3.2c) + ['papá', 'papás'], + ['café', 'cafés'], + ['isrealí', 'isrealís'], + ['buró', 'burós'], + ['tisú', 'tisús'], + + // ending in -ión + ['avión', 'aviones'], + ['camión', 'camiones'], + + // ending in some letters (RAE 3.2k) + ['amor', 'amores'], + ['antifaz', 'antifaces'], + ['atril', 'atriles'], + ['fácsimil', 'fácsimiles'], + ['vid', 'vides'], + ['rey', 'reyes'], + ['reloj', 'relojes'], + ['fax', 'faxes'], + ['sándwich', 'sándwiches'], + ['cánon', 'cánones'], + + // (RAE 3.2n) + ['adiós', 'adioses'], + ['aguarrás', 'aguarrases'], + ['arnés', 'arneses'], + ['autobús', 'autobuses'], + ['kermés', 'kermeses'], + ['palmarés', 'palmareses'], + ['tos', 'toses'], + + // Specials + ['sí', 'síes'], + ['no', 'noes'], + ]; + } + + public static function uninflectedProvider(): array + { + return [ + ['lunes'], + ['rodapiés'], + ['reposapiés'], + ['miércoles'], + ['pies'], + ]; + } + + /** + * @dataProvider singularizeProvider + */ + public function testSingularize(string $plural, $singular) + { + $this->assertSame( + \is_array($singular) ? $singular : [$singular], + (new SpanishInflector())->singularize($plural) + ); + } + + /** + * @dataProvider pluralizeProvider + */ + public function testPluralize(string $singular, $plural) + { + $this->assertSame( + \is_array($plural) ? $plural : [$plural], + (new SpanishInflector())->pluralize($singular) + ); + } + + /** + * @dataProvider uninflectedProvider + */ + public function testUninflected(string $word) + { + $this->assertSame( + \is_array($word) ? $word : [$word], + (new SpanishInflector())->pluralize($word) + ); + } +} From 8133473e9c048c97c698d6606efe2fe4ca629657 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 25 Sep 2024 13:30:45 +0200 Subject: [PATCH 65/68] [String] Add the `AbstractString::kebab()` method --- AbstractString.php | 5 +++++ CHANGELOG.md | 1 + Tests/AbstractAsciiTestCase.php | 29 +++++++++++++++++++++++++++++ Tests/AbstractUnicodeTestCase.php | 9 +++++++++ 4 files changed, 44 insertions(+) diff --git a/AbstractString.php b/AbstractString.php index 81636c4..500d7c3 100644 --- a/AbstractString.php +++ b/AbstractString.php @@ -433,6 +433,11 @@ abstract public function slice(int $start = 0, ?int $length = null): static; abstract public function snake(): static; + public function kebab(): static + { + return $this->snake()->replace('_', '-'); + } + abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static; /** diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f7204..ff505b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `TruncateMode` enum to handle more truncate methods + * Add the `AbstractString::kebab()` method 7.1 --- diff --git a/Tests/AbstractAsciiTestCase.php b/Tests/AbstractAsciiTestCase.php index e046255..ee4890f 100644 --- a/Tests/AbstractAsciiTestCase.php +++ b/Tests/AbstractAsciiTestCase.php @@ -1089,6 +1089,35 @@ public static function provideSnake() ]; } + /** + * @dataProvider provideKebab + */ + public function testKebab(string $expectedString, string $origin) + { + $instance = static::createFromString($origin)->kebab(); + + $this->assertEquals(static::createFromString($expectedString), $instance); + $this->assertNotSame($origin, $instance, 'Strings should be immutable'); + } + + public static function provideKebab(): array + { + return [ + ['', ''], + ['x-y', 'x_y'], + ['x-y', 'X_Y'], + ['xu-yo', 'xu_yo'], + ['symfony-is-great', 'symfonyIsGreat'], + ['symfony123-is-great', 'symfony123IsGreat'], + ['symfony123is-great', 'symfony123isGreat'], + ['symfony-is-great', 'Symfony is great'], + ['symfony-is-a-great-framework', 'symfonyIsAGreatFramework'], + ['symfony-is-great', 'symfonyIsGREAT'], + ['symfony-is-really-great', 'symfonyIsREALLYGreat'], + ['symfony', 'SYMFONY'], + ]; + } + /** * @dataProvider provideStartsWith */ diff --git a/Tests/AbstractUnicodeTestCase.php b/Tests/AbstractUnicodeTestCase.php index e838c44..bde19d7 100644 --- a/Tests/AbstractUnicodeTestCase.php +++ b/Tests/AbstractUnicodeTestCase.php @@ -665,6 +665,15 @@ public static function provideSnake() ); } + public static function provideKebab(): array + { + return [ + ...parent::provideKebab(), + ['symfony-ist-äußerst-cool', 'symfonyIstÄußerstCool'], + ['symfony-with-emojis', 'Symfony with 😃 emojis'], + ]; + } + public static function provideEqualsTo() { return array_merge( From 205580699b4d3e11f7b679faf2c0f57ffca6981c Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 18 Oct 2024 16:04:52 +0200 Subject: [PATCH 66/68] Remove always true/false occurrences --- Tests/Inflector/SpanishInflectorTest.php | 2 +- UnicodeString.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Inflector/SpanishInflectorTest.php b/Tests/Inflector/SpanishInflectorTest.php index f0b8e42..b10509a 100644 --- a/Tests/Inflector/SpanishInflectorTest.php +++ b/Tests/Inflector/SpanishInflectorTest.php @@ -151,7 +151,7 @@ public function testPluralize(string $singular, $plural) public function testUninflected(string $word) { $this->assertSame( - \is_array($word) ? $word : [$word], + [$word], (new SpanishInflector())->pluralize($word) ); } diff --git a/UnicodeString.php b/UnicodeString.php index 4b16caf..b458de0 100644 --- a/UnicodeString.php +++ b/UnicodeString.php @@ -286,7 +286,7 @@ public function splice(string $replacement, int $start = 0, ?int $length = null) $str = clone $this; $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; - $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; + $length = $length ? \strlen(grapheme_substr($this->string, $start, $length)) : $length; $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); if (normalizer_is_normalized($str->string)) { From 71c369598bfa44bb9815fc26b26e592d1d989be1 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 17 Apr 2025 13:34:07 +0200 Subject: [PATCH 67/68] ignore the current locale before transliterating ASCII codes with iconv() --- AbstractUnicodeString.php | 22 ++++++++++++++-------- Tests/Slugger/AsciiSluggerTest.php | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/AbstractUnicodeString.php b/AbstractUnicodeString.php index 70598e4..bd84b25 100644 --- a/AbstractUnicodeString.php +++ b/AbstractUnicodeString.php @@ -135,15 +135,21 @@ public function ascii(array $rules = []): self } elseif (!\function_exists('iconv')) { $s = preg_replace('/[^\x00-\x7F]/u', '?', $s); } else { - $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { - $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); - - if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { - throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); - } + $previousLocale = setlocale(\LC_CTYPE, 0); + try { + setlocale(\LC_CTYPE, 'C'); + $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { + $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); + + if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { + throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); + } - return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); - }, $s); + return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); + }, $s); + } finally { + setlocale(\LC_CTYPE, $previousLocale); + } } } diff --git a/Tests/Slugger/AsciiSluggerTest.php b/Tests/Slugger/AsciiSluggerTest.php index 703212f..7a6c06a 100644 --- a/Tests/Slugger/AsciiSluggerTest.php +++ b/Tests/Slugger/AsciiSluggerTest.php @@ -106,4 +106,19 @@ public static function provideSlugEmojiTests(): iterable 'undefined_locale', // Behaves the same as if emoji support is disabled ]; } + + /** + * @requires extension intl + */ + public function testSlugEmojiWithSetLocale() + { + if (!setlocale(LC_ALL, 'C.UTF-8')) { + $this->markTestSkipped('Unable to switch to the "C.UTF-8" locale.'); + } + + $slugger = new AsciiSlugger(); + $slugger = $slugger->withEmoji(true); + + $this->assertSame('a-and-a-go-to', (string) $slugger->slug('a 😺, 🐈‍⬛, and a 🦁 go to 🏞️... 😍 🎉 💛', '-')); + } } From 73e2c6966a5aef1d4892873ed5322245295370c6 Mon Sep 17 00:00:00 2001 From: Korvin Szanto Date: Thu, 17 Apr 2025 09:33:24 -0700 Subject: [PATCH 68/68] Support nexus -> nexuses pluralization --- Inflector/EnglishInflector.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Inflector/EnglishInflector.php b/Inflector/EnglishInflector.php index a5be28d..73db80c 100644 --- a/Inflector/EnglishInflector.php +++ b/Inflector/EnglishInflector.php @@ -333,6 +333,9 @@ final class EnglishInflector implements InflectorInterface // conspectuses (conspectus), prospectuses (prospectus) ['sutcep', 6, true, true, 'pectuses'], + // nexuses (nexus) + ['suxen', 5, false, false, 'nexuses'], + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) ['su', 2, true, true, 'i'],