Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ parameters:
-
message: '#^Access to an undefined property Phaseolies\\Database\\Entity\\Builder\:\:\$attributes\.$#'
identifier: property.notFound
count: 3
count: 4
path: src/Phaseolies/Database/Entity/Builder.php

-
Expand All @@ -507,7 +507,7 @@ parameters:
-
message: '#^Access to an undefined property Phaseolies\\Database\\Entity\\Builder\:\:\$originalAttributes\.$#'
identifier: property.notFound
count: 3
count: 4
path: src/Phaseolies/Database/Entity/Builder.php

-
Expand Down
163 changes: 110 additions & 53 deletions src/Phaseolies/Support/StringService.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ public function len(string $input): int
*/
public function countWord(string $string): int
{
preg_match_all('/\p{L}[\p{L}\p{Mn}\p{Pd}\'’]*/u', $string, $matches);
// A word starts with a letter or digit. A "." or "," only continues it
// between two digits, so 3.14 and 1,000 count as one word.
preg_match_all('/[\p{L}\p{Nd}](?:[\p{L}\p{Nd}\p{Mn}\p{Pd}\'’]|(?<=\p{Nd})[.,](?=\p{Nd}))*/u', $string, $matches);

return count($matches[0]);
}
Expand All @@ -51,9 +53,9 @@ public function countWord(string $string): int
*/
public function isPalindrome(string $string): bool
{
$cleaned = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $string));
$cleaned = mb_strtolower(preg_replace('/[^\p{L}\p{Nd}]/u', '', $string) ?? '', 'UTF-8');

return $cleaned === strrev($cleaned);
return $cleaned === $this->reverse($cleaned);
}

/**
Expand All @@ -64,7 +66,15 @@ public function isPalindrome(string $string): bool
*/
public function random(int $length = 10): string
{
return substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', $length)), 0, $length);
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$last = strlen($alphabet) - 1;
$result = '';

for ($i = 0; $i < $length; $i++) {
$result .= $alphabet[random_int(0, $last)];
}

return $result;
}

/**
Expand All @@ -75,11 +85,9 @@ public function random(int $length = 10): string
*/
public function camel(string $input): string
{
$input = str_replace(['-', '_'], ' ', $input);
$input = mb_convert_case($input, MB_CASE_TITLE, 'UTF-8');
$input = str_replace(' ', '', $input);
$studly = $this->studly($input);

return lcfirst($input);
return mb_strtolower(mb_substr($studly, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($studly, 1, null, 'UTF-8');
}

/**
Expand All @@ -88,18 +96,23 @@ public function camel(string $input): string
* @param string $string
* @param int $visibleFromStart
* @param int $visibleFromEnd
* @param string
* @param string $maskCharacter
* @return string
*/
public function mask(string $string, int $visibleFromStart = 1, int $visibleFromEnd = 1, string $maskCharacter = '*'): string
{
$length = strlen($string);
$visibleFromStart = max(0, $visibleFromStart);
$visibleFromEnd = max(0, $visibleFromEnd);

$length = mb_strlen($string, 'UTF-8');

if ($length <= $visibleFromStart + $visibleFromEnd) {
return $string;
}

$startPart = substr($string, 0, $visibleFromStart);
$endPart = substr($string, -$visibleFromEnd);
$startPart = mb_substr($string, 0, $visibleFromStart, 'UTF-8');

$endPart = $visibleFromEnd > 0 ? mb_substr($string, -$visibleFromEnd, null, 'UTF-8') : '';
$middlePart = str_repeat($maskCharacter, $length - $visibleFromStart - $visibleFromEnd);

return $startPart . $middlePart . $endPart;
Expand All @@ -115,7 +128,11 @@ public function mask(string $string, int $visibleFromStart = 1, int $visibleFrom
*/
public function truncate(string $string, int $maxLength, string $suffix = '...'): string
{
return (strlen($string) > $maxLength) ? substr($string, 0, $maxLength) . $suffix : $string;
$maxLength = max(0, $maxLength);

return (mb_strlen($string, 'UTF-8') > $maxLength)
? mb_substr($string, 0, $maxLength, 'UTF-8') . $suffix
: $string;
}

/**
Expand All @@ -126,7 +143,7 @@ public function truncate(string $string, int $maxLength, string $suffix = '...')
*/
public function snake(string $input): string
{
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $input));
return mb_strtolower(preg_replace('/([\p{Ll}\p{Nd}])(\p{Lu})/u', '$1_$2', $input) ?? $input, 'UTF-8');
}

/**
Expand All @@ -150,11 +167,12 @@ public function title(string $input): string
public function slug(string $input, string $separator = '-'): string
{
$slug = mb_strtolower($input, 'UTF-8');
$slug = preg_replace('/[\s,\.;\'"!\?]+/u', $separator, $slug);
$slug = preg_replace('/[^\p{L}\p{Nd}' . preg_quote($separator, '/') . ']+/u', '', $slug);
$slug = preg_replace('/' . preg_quote($separator, '/') . '{2,}/u', $separator, $slug);

return trim($slug, $separator);
$slug = preg_replace('/\p{M}+/u', '', $slug) ?? $slug;

$slug = preg_replace('/[^\p{L}\p{Nd}]+/u', "\0", $slug) ?? $slug;

return str_replace("\0", $separator, trim($slug, "\0"));
}

/**
Expand All @@ -176,6 +194,10 @@ public function contains(string $haystack, string | array $needles, bool $ignore
return false;
}

if ($needles === '') {
return false;
}

if ($ignoreCase) {
return mb_stripos($haystack, $needles, 0, 'UTF-8') !== false;
}
Expand All @@ -201,7 +223,10 @@ public function limitWords(string $string, int $words, string $end = '...', stri
return '';
}

$wordArray = preg_split('/\s+/u', $trimmed);
$words = max(0, $words);

// Fall back to a byte-wise split when the string is not valid UTF-8.
$wordArray = preg_split('/\s+/u', $trimmed) ?: preg_split('/\s+/', $trimmed);

// If the total number of words is less than or equal to $words, return original.
if (count($wordArray) <= $words) {
Expand Down Expand Up @@ -252,17 +277,13 @@ public function removeWhiteSpace(string $input): string
*/
public function uuid(): string
{
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
$bytes = random_bytes(16);

// Set the version (4) and variant (RFC 4122) bits.
$bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40);
$bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);

return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
}

/**
Expand Down Expand Up @@ -336,11 +357,24 @@ public function endsWith(string $haystack, string | array $needles, string $enco
*/
public function studly(string $input, string $encoding = 'UTF-8'): string
{
$spaced = str_replace(['-', '_'], ' ', $input);
$segments = preg_split('/([-_\s]+)/u', $input, -1, PREG_SPLIT_DELIM_CAPTURE) ?: [$input];

$titled = mb_convert_case($spaced, MB_CASE_TITLE, $encoding);
$studly = '';

$studly = str_replace(' ', '', $titled);
foreach ($segments as $segment) {
if ($segment === '' || preg_match('/^[-_\s]+$/u', $segment)) {
continue;
}

$first = mb_substr($segment, 0, 1, $encoding);
$rest = mb_substr($segment, 1, null, $encoding);

// A word that already has humps (fooBar, iPhone, XMLParser) keeps
// them; any other word is normalised (HELLO, hello -> Hello).
$studly .= preg_match('/\p{Ll}/u', $rest) && preg_match('/\p{Lu}/u', $rest)
? mb_strtoupper($first, $encoding) . $rest
: mb_convert_case($segment, MB_CASE_TITLE, $encoding);
}

return $studly;
}
Expand Down Expand Up @@ -386,23 +420,31 @@ public function extractNumbers(string $input): string
*/
public function longestCommonSubstring(string $str1, string $str2): string
{
$matrix = array_fill(0, strlen($str1) + 1, array_fill(0, strlen($str2) + 1, 0));
$chars1 = mb_str_split($str1, 1, 'UTF-8');
$chars2 = mb_str_split($str2, 1, 'UTF-8');
$length2 = count($chars2);
$maxLength = 0;
$endIndex = 0;
$previous = array_fill(0, $length2 + 1, 0);

foreach ($chars1 as $i => $char1) {
$current = array_fill(0, $length2 + 1, 0);

for ($i = 1; $i <= strlen($str1); $i++) {
for ($j = 1; $j <= strlen($str2); $j++) {
if ($str1[$i - 1] === $str2[$j - 1]) {
$matrix[$i][$j] = $matrix[$i - 1][$j - 1] + 1;
if ($matrix[$i][$j] > $maxLength) {
$maxLength = $matrix[$i][$j];
$endIndex = $i;
foreach ($chars2 as $j => $char2) {
if ($char1 === $char2) {
$current[$j + 1] = $previous[$j] + 1;

if ($current[$j + 1] > $maxLength) {
$maxLength = $current[$j + 1];
$endIndex = $i + 1;
}
}
}

$previous = $current;
}

return substr($str1, $endIndex - $maxLength, $maxLength);
return implode('', array_slice($chars1, $endIndex - $maxLength, $maxLength));
}

/**
Expand Down Expand Up @@ -448,15 +490,15 @@ public function extractEmails(string $input): array
*/
public function highlightKeyword(string $input, string $keyword, string $tag = 'strong'): string
{
if (empty($keyword)) {
if ($keyword === '') {
return $input;
}

return preg_replace(
'/(' . preg_quote($keyword, '/') . ')/i',
"<$tag>$1</$tag>",
$input
);
$pattern = '/(' . preg_quote($keyword, '/') . ')/i';

return preg_replace($pattern . 'u', "<$tag>$1</$tag>", $input)
?? preg_replace($pattern, "<$tag>$1</$tag>", $input)
?? $input;
}

/**
Expand All @@ -467,7 +509,7 @@ public function highlightKeyword(string $input, string $keyword, string $tag = '
*/
public function toUpper(string $input): string
{
return strtoupper($input);
return mb_strtoupper($input, 'UTF-8');
}

/**
Expand All @@ -479,11 +521,15 @@ public function toUpper(string $input): string
*/
public function suffixAppend(string $input, string $suffix): string
{
if (str()->endsWith($input, $suffix)) {
if ($this->endsWith($input, $suffix)) {
return $input;
}

return strtoupper($input[0]) . substr($input, 1) . $suffix;
if ($input === '') {
return $suffix;
}

return mb_strtoupper(mb_substr($input, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($input, 1, null, 'UTF-8') . $suffix;
}

/**
Expand All @@ -495,7 +541,11 @@ public function suffixAppend(string $input, string $suffix): string
*/
public function removeSuffix(string $input, string $suffix): string
{
return substr($input, 0, -strlen($suffix));
if ($suffix === '' || ! $this->endsWith($input, $suffix)) {
return $input;
}

return mb_substr($input, 0, mb_strlen($input, 'UTF-8') - mb_strlen($suffix, 'UTF-8'), 'UTF-8');
}

/**
Expand Down Expand Up @@ -544,8 +594,15 @@ public function before(string $subject, string $search): string
*/
public function between(string $subject, string $from, string $to): string
{
if ($from === '' || $to === '') {
return $subject;
}

$start = $this->after($subject, $from);
return $this->before($start, $to);
$pos = mb_strrpos($start, $to, 0, 'UTF-8');

// Up to the last closing match, so a nested "[a[b]c]" yields "a[b]c".
return $pos === false ? $start : mb_substr($start, 0, $pos, 'UTF-8');
}

/**
Expand Down
Loading
Loading