$val) { $value = str_replace($val, (string) $key, $value); } return preg_replace('/[^\x20-\x7E]/u', '', $value); } /** * Get the portion of a string before a given value. * * @param string $subject * @param string $search * @return string */ public static function before($subject, $search) { return $search === '' ? $subject : explode($search, $subject)[0]; } /** * Get the portion of a string before the last occurrence of a given value. * * @param string $subject * @param string $search * @return string */ public static function beforeLast($subject, $search) { if ($search === '') { return $subject; } $pos = mb_strrpos($subject, $search); if ($pos === false) { return $subject; } return static::substr($subject, 0, $pos); } /** * Get the portion of a string between two given values. * * @param string $subject * @param string $from * @param string $to * @return string */ public static function between($subject, $from, $to) { if ($from === '' || $to === '') { return $subject; } return static::beforeLast(static::after($subject, $from), $to); } /** * Convert a value to camel case. * * @param string $value * @return string */ public static function camel($value) { return lcfirst(static::studly($value)); } /** * Get the character at the specified index. * * @param string $subject * @param int $index * @return null|string */ public static function charAt($subject, $index) { $length = mb_strlen($subject); if ($index < 0 ? $index < -$length : $index > $length - 1) { return null; } return mb_substr($subject, $index, 1); } /** * Determine if a given string contains a given substring. * * @param array|string $needles */ public static function contains(string $haystack, mixed $needles, bool $ignoreCase = false): bool { if ($ignoreCase) { return static::containsIgnoreCase($haystack, $needles); } foreach ((array) $needles as $needle) { $needle = (string) $needle; if ($needle !== '' && str_contains($haystack, $needle)) { return true; } } return false; } /** * Determine if a given string contains a given substring regardless of case sensitivity. * * @param array|string $needles */ public static function containsIgnoreCase(string $haystack, $needles): bool { foreach ((array) $needles as $needle) { $needle = (string) $needle; if ($needle !== '' && stripos($haystack, $needle) !== false) { return true; } } return false; } /** * Determine if a given string contains all array values. * * @param string[] $needles * @return bool */ public static function containsAll(string $haystack, array $needles, bool $ignoreCase = false) { foreach ($needles as $needle) { if (! static::contains($haystack, $needle, $ignoreCase)) { return false; } } return true; } /** * Determine if a given string ends with a given substring. * * @param array|string $needles * @return bool */ public static function endsWith(string $haystack, $needles) { foreach ((array) $needles as $needle) { $needle = (string) $needle; if ($needle !== '' && str_ends_with($haystack, $needle)) { return true; } } return false; } /** * Cap a string with a single instance of a given value. * * @param string $value * @param string $cap * @return string */ public static function finish($value, $cap) { $quoted = preg_quote($cap, '/'); return preg_replace('/(?:' . $quoted . ')+$/u', '', $value) . $cap; } /** * Determine if a given string matches a given pattern. * * @param array|string $pattern * @param string $value * @return bool */ public static function is($pattern, $value) { $patterns = Arr::wrap($pattern); if (empty($patterns)) { return false; } foreach ($patterns as $pattern) { // If the given value is an exact match we can of course return true right // from the beginning. Otherwise, we will translate asterisks and do an // actual pattern match against the two strings to see if they match. if ($pattern == $value) { return true; } $pattern = preg_quote($pattern, '#'); // Asterisks are translated into zero-or-more regular expression wildcards // to make it convenient to check if the strings starts with the given // pattern such as "library/*", making any string check convenient. $pattern = str_replace('\*', '.*', $pattern); if (preg_match('#^' . $pattern . '\z#u', $value) === 1) { return true; } } return false; } /** * Determine if a given string is 7 bit ASCII. */ public static function isAscii(string $value): bool { if ($value == '') { return true; } return ! preg_match('/[^\x09\x0A\x0D\x20-\x7E]/', $value); } /** * Convert a string to kebab case. * * @param string $value * @return string */ public static function kebab($value) { return static::snake($value, '-'); } /** * Return the length of the given string. * * @param string $value * @param string $encoding * @return int */ public static function length($value, $encoding = null) { return mb_strlen($value, $encoding); } /** * Limit the number of characters in a string. * * @param string $value * @param int $limit * @param string $end * @return string */ public static function limit($value, $limit = 100, $end = '...') { if (mb_strwidth($value, 'UTF-8') <= $limit) { return $value; } return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')) . $end; } /** * Convert the given string to lower-case. * * @param string $value * @return string */ public static function lower($value) { return mb_strtolower($value, 'UTF-8'); } /** * Limit the number of words in a string. */ public static function words(string $value, int $words = 100, string $end = '...'): string { preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $value, $matches); if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) { return $value; } return rtrim($matches[0]) . $end; } /** * Get the string matching the given pattern. * * @param string $pattern * @param string $subject * @return string */ public static function match($pattern, $subject) { preg_match($pattern, $subject, $matches); if (! $matches) { return ''; } return $matches[1] ?? $matches[0]; } /** * Determine if a given string matches a given pattern. * * @param iterable|string $patterns * @param string $value * @return bool */ public static function isMatch($patterns, $value) { $value = (string) $value; if (! is_iterable($patterns)) { $patterns = [$patterns]; } foreach ($patterns as $pattern) { $pattern = (string) $pattern; if (preg_match($pattern, $value) === 1) { return true; } } return false; } /** * Get the string matching the given pattern. * * @param string $pattern * @param string $subject * @return Collection */ public static function matchAll($pattern, $subject) { preg_match_all($pattern, $subject, $matches); if (empty($matches[0])) { return collect(); } return collect($matches[1] ?? $matches[0]); } /** * Pad both sides of a string with another. * * @param string $value * @param int $length * @param string $pad * @return string */ public static function padBoth($value, $length, $pad = ' ') { return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_BOTH); } /** * Pad the left side of a string with another. * * @param string $value * @param int $length * @param string $pad * @return string */ public static function padLeft($value, $length, $pad = ' ') { return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_LEFT); } /** * Pad the right side of a string with another. * * @param string $value * @param int $length * @param string $pad * @return string */ public static function padRight($value, $length, $pad = ' ') { return str_pad($value, strlen($value) - mb_strlen($value) + $length, $pad, STR_PAD_RIGHT); } /** * Parse a Class@method style callback into class and method. * * @param null|string $default */ public static function parseCallback(string $callback, $default = null): array { return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; } /** * Pluralize the last word of an English, studly caps case string. * @param mixed $count */ public static function pluralStudly(string $value, $count = 2): string { $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE); $lastWord = array_pop($parts); return implode('', $parts) . self::plural($lastWord, $count); } /** * Get the plural form of an English word. */ public static function plural(string $value, array|Countable|int $count = 2): string { return Pluralizer::plural($value, $count); } /** * Find the multi-byte safe position of the first occurrence of a given substring in a string. */ public static function position(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): false|int { return mb_strpos($haystack, (string) $needle, $offset, $encoding); } /** * Generate a more truly "random" alpha-numeric string. */ public static function random(int $length = 16): string { $string = ''; while (($len = strlen($string)) < $length) { $size = $length - $len; $bytes = random_bytes($size); $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); } return $string; } /** * Repeat the given string. * * @return string */ public static function repeat(string $string, int $times) { return str_repeat($string, $times); } /** * Replace a given value in the string sequentially with an array. * * @param string[] $replace */ public static function replaceArray(string $search, array $replace, string $subject): string { foreach ($replace as $value) { $subject = static::replaceFirst($search, (string) $value, $subject); } return $subject; } /** * Replace the given value in the given string. * * @param string|string[] $search * @param string|string[] $replace * @param string|string[] $subject * @return string */ public static function replace($search, $replace, $subject) { return str_replace($search, $replace, $subject); } /** * Replace the first occurrence of a given value in the string. */ public static function replaceFirst(string $search, string $replace, string $subject): string { if ($search == '') { return $subject; } $position = strpos($subject, $search); if ($position !== false) { return substr_replace($subject, $replace, $position, strlen($search)); } return $subject; } /** * Replace the last occurrence of a given value in the string. */ public static function replaceLast(string $search, string $replace, string $subject): string { if ($search == '') { return $subject; } $position = strrpos($subject, $search); if ($position !== false) { return substr_replace($subject, $replace, $position, strlen($search)); } return $subject; } /** * Remove any occurrence of the given string in the subject. * * @param array|string $search * @param string $subject * @param bool $caseSensitive * @return string */ public static function remove($search, $subject, $caseSensitive = true) { return $caseSensitive ? str_replace($search, '', $subject) : str_ireplace($search, '', $subject); } /** * Begin a string with a single instance of a given value. */ public static function start(string $value, string $prefix): string { $quoted = preg_quote($prefix, '/'); return $prefix . preg_replace('/^(?:' . $quoted . ')+/u', '', $value); } /** * Strip HTML and PHP tags from the given string. * * @param null|string|string[] $allowedTags */ public static function stripTags(string $value, $allowedTags = null): string { return strip_tags($value, $allowedTags); } /** * Convert the given string to upper-case. */ public static function upper(string $value): string { return mb_strtoupper($value, 'UTF-8'); } /** * Convert the given string to title case. */ public static function title(string $value): string { return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); } /** * Convert the given string to proper case for each word. */ public static function headline(string $value): string { $parts = explode(' ', $value); $parts = count($parts) > 1 ? array_map([static::class, 'title'], $parts) : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts))); $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts)); return implode(' ', array_filter(explode('_', $collapsed))); } /** * Get the singular form of an English word. */ public static function singular(string $value): string { return Pluralizer::singular($value); } /** * Generate a URL friendly "slug" from a given string. * @param mixed $dictionary */ public static function slug(string $title, string $separator = '-', ?string $language = 'en', $dictionary = ['@' => 'at']): string { $title = $language ? static::ascii($title, $language) : $title; // Convert all dashes/underscores into separator $flip = $separator === '-' ? '_' : '-'; $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title); // Replace dictionary words foreach ($dictionary as $key => $value) { $dictionary[$key] = $separator . $value . $separator; } $title = str_replace(array_keys($dictionary), array_values($dictionary), $title); // Remove all characters that are not the separator, letters, numbers, or whitespace. $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title)); // Replace all separator characters and whitespace by a single separator $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title); return trim($title, $separator); } /** * Convert a string to snake case. */ public static function snake(string $value, string $delimiter = '_'): string { if (! ctype_lower($value)) { $value = preg_replace('/\s+/u', '', ucwords($value)); $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); } return $value; } /** * Determine if a given string starts with a given substring. * * @param array|string $needles */ public static function startsWith(string $haystack, $needles): bool { foreach ((array) $needles as $needle) { $needle = (string) $needle; if ($needle !== '' && str_starts_with($haystack, $needle)) { return true; } } return false; } /** * Convert a value to studly caps case. */ public static function studly(string $value, string $gap = ''): string { $value = ucwords(str_replace(['-', '_'], ' ', $value)); return str_replace(' ', $gap, $value); } /** * Returns the portion of string specified by the start and length parameters. * * @param string $string * @param int $start * @param null|int $length * @return string */ public static function substr($string, $start, $length = null) { return mb_substr($string, $start, $length, 'UTF-8'); } /** * Returns the number of substring occurrences. * * @param string $haystack * @param string $needle * @param int $offset * @param null|int $length * @return int */ public static function substrCount($haystack, $needle, $offset = 0, $length = null) { if (! is_null($length)) { return substr_count($haystack, $needle, $offset, $length); } return substr_count($haystack, $needle, $offset); } /** * Make a string's first character uppercase. */ public static function ucfirst(string $string): string { return static::upper(static::substr($string, 0, 1)) . static::substr($string, 1); } /** * Replaces the first or the last ones chars from a string by a given char. * * @param int $offset if is negative it starts from the end * @param string $replacement default is * * @return string */ public static function mask(string $string, int $offset = 0, int $length = 0, string $replacement = '*') { if ($length < 0) { throw new InvalidArgumentException('The length must equal or greater than zero.'); } $stringLength = mb_strlen($string); $absOffset = abs($offset); if ($absOffset >= $stringLength) { return $string; } $hiddenLength = $length ?: $stringLength - $absOffset; if ($offset >= 0) { return mb_substr($string, 0, $offset) . str_repeat($replacement, $hiddenLength) . mb_substr($string, $offset + $hiddenLength); } return mb_substr($string, 0, max($stringLength - $hiddenLength - $absOffset, 0)) . str_repeat($replacement, $hiddenLength) . mb_substr($string, $offset); } /** * Determine if a given value is a valid ULID. * * @param mixed $value */ public static function isUlid($value): bool { if (! is_string($value)) { return false; } if (strlen($value) !== 26) { return false; } if (strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz') !== 26) { return false; } return $value[0] <= '7'; } /** * Generate a ULID. */ public static function ulid(?DateTimeInterface $time = null): Ulid { if (! class_exists(Ulid::class)) { throw new RuntimeException('The "symfony/uid" package is required to use the "ulid" method. Please run "composer require symfony/uid".'); } return new Ulid(Ulid::generate($time)); } /** * Determine if a given value is a valid URL. * * @param string $value */ public static function isUrl($value, array $protocols = []): bool { if (! is_string($value)) { return false; } $protocolList = empty($protocols) ? 'aaa|aaas|about|acap|acct|acd|acr|adiumxtra|adt|afp|afs|aim|amss|android|appdata|apt|ark|attachment|aw|barion|beshare|bitcoin|bitcoincash|blob|bolo|browserext|calculator|callto|cap|cast|casts|chrome|chrome-extension|cid|coap|coap\+tcp|coap\+ws|coaps|coaps\+tcp|coaps\+ws|com-eventbrite-attendee|content|conti|crid|cvs|dab|data|dav|diaspora|dict|did|dis|dlna-playcontainer|dlna-playsingle|dns|dntp|dpp|drm|drop|dtn|dvb|ed2k|elsi|example|facetime|fax|feed|feedready|file|filesystem|finger|first-run-pen-experience|fish|fm|ftp|fuchsia-pkg|geo|gg|git|gizmoproject|go|gopher|graph|gtalk|h323|ham|hcap|hcp|http|https|hxxp|hxxps|hydrazone|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris\.beep|iris\.lwz|iris\.xpc|iris\.xpcs|isostore|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|leaptofrogans|lorawan|lvlt|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|mongodb|moz|ms-access|ms-browser-extension|ms-calculator|ms-drive-to|ms-enrollment|ms-excel|ms-eyecontrolspeech|ms-gamebarservices|ms-gamingoverlay|ms-getoffice|ms-help|ms-infopath|ms-inputapp|ms-lockscreencomponent-config|ms-media-stream-id|ms-mixedrealitycapture|ms-mobileplans|ms-officeapp|ms-people|ms-project|ms-powerpoint|ms-publisher|ms-restoretabcompanion|ms-screenclip|ms-screensketch|ms-search|ms-search-repair|ms-secondary-screen-controller|ms-secondary-screen-setup|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-connectabledevices|ms-settings-displays-topology|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|ms-spd|ms-sttoverlay|ms-transit-to|ms-useractivityset|ms-virtualtouchpad|ms-visio|ms-walk-to|ms-whiteboard|ms-whiteboard-cmd|ms-word|msnim|msrp|msrps|mss|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|ocf|oid|onenote|onenote-cmd|opaquelocktoken|openpgp4fpr|pack|palm|paparazzi|payto|pkcs11|platform|pop|pres|prospero|proxy|pwid|psyc|pttp|qb|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|s3|secondlife|service|session|sftp|sgn|shttp|sieve|simpleledger|sip|sips|skype|smb|sms|smtp|snews|snmp|soap\.beep|soap\.beeps|soldat|spiffe|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|tg|things|thismessage|tip|tn3270|tool|ts3server|turn|turns|tv|udp|unreal|urn|ut2004|v-event|vemmi|ventrilo|videotex|vnc|view-source|wais|webcal|wpid|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc\.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s' : implode('|', $protocols); /* * This pattern is derived from Symfony\Component\Validator\Constraints\UrlValidator (5.0.7). * * (c) Fabien Potencier http://symfony.com */ $pattern = '~^ (DEFAULT_PROTOCOLS):// # protocol (((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+)@)? # basic auth ( ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name | # or \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address | # or \[ (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) \] # an IPv6 address ) (:[0-9]+)? # a port (optional) (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})* )* # a path (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )? # a query (optional) (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )? # a fragment (optional) $~ixu'; return preg_match(str_replace('DEFAULT_PROTOCOLS', $protocolList, $pattern), $value) > 0; } /** * Determine if a given value is a valid UUID. * * @param mixed $value */ public static function isUuid($value): bool { if (! is_string($value)) { return false; } return preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0; } /** * Generate a UUID (version 4). */ public static function uuid(): UuidInterface { if (! class_exists(Uuid::class)) { throw new RuntimeException('The "ramsey/uuid" package is required to use the "uuid" method. Please run "composer require ramsey/uuid".'); } return Uuid::uuid4(); } /** * Generate a time-ordered UUID. */ public static function orderedUuid(?DateTimeInterface $time = null): UuidInterface { if (! class_exists(Uuid::class)) { throw new RuntimeException('The "ramsey/uuid" package is required to use the "orderedUuid" method. Please run "composer require ramsey/uuid".'); } return Uuid::uuid7($time); } /** * Get the smallest possible portion of a string between two given values. * * @param string $subject * @param string $from * @param string $to * @return string */ public static function betweenFirst($subject, $from, $to) { if ($from === '' || $to === '') { return $subject; } return Str::before(Str::after($subject, $from), $to); } /** * @param string $value */ public static function classNamespace($value): string { if ($pos = strrpos($value, '\\')) { return substr($value, 0, $pos); } return ''; } /** * Convert the case of a string. */ public static function convertCase(string $string, int $mode = MB_CASE_FOLD, ?string $encoding = 'UTF-8'): string { return mb_convert_case($string, $mode, $encoding); } /** * Extracts an excerpt from text that matches the first instance of a phrase. * * @param string $text * @param string $phrase * @param array $options * @return null|string */ public static function excerpt($text, $phrase = '', $options = []) { $radius = $options['radius'] ?? 100; $omission = $options['omission'] ?? '...'; preg_match('/^(.*?)(' . preg_quote((string) $phrase) . ')(.*)$/iu', (string) $text, $matches); if (empty($matches)) { return null; } $startStr = ltrim($matches[1]); $start = Str::of(mb_substr($matches[1], max(mb_strlen($startStr, 'UTF-8') - $radius, 0), $radius, 'UTF-8'))->ltrim(); $start = $start->unless( (fn ($startWithRadius) => $startWithRadius->exactly($startStr))($start), fn ($startWithRadius) => $startWithRadius->prepend($omission), ); $endStr = rtrim($matches[3]); $end = Str::of(mb_substr($endStr, 0, $radius, 'UTF-8'))->rtrim(); $end = $end->unless( (fn ($endWithRadius) => $endWithRadius->exactly($endStr))($end), fn ($endWithRadius) => $endWithRadius->append($omission), ); return $start->append($matches[2], (string) $end)->__toString(); } /** * Determine if a given value is valid JSON. * * @param mixed $value */ public static function isJson($value): bool { if (! is_string($value)) { return false; } if (function_exists('json_validate')) { return json_validate($value, 512); } try { json_decode($value, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { return false; } return true; } /** * Make a string's first character lowercase. * * @param string $string */ public static function lcfirst($string): string { return Str::lower(Str::substr($string, 0, 1)) . Str::substr($string, 1); } /** * Generate a random, secure password. * * @param int $length * @param bool $letters * @param bool $numbers * @param bool $symbols * @param bool $spaces * @return string */ public static function password($length = 32, $letters = true, $numbers = true, $symbols = true, $spaces = false) { return (new Collection()) ->when($letters, fn ($c) => $c->merge([ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ])) ->when($numbers, fn ($c) => $c->merge([ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ])) ->when($symbols, fn ($c) => $c->merge([ '~', '!', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '.', ',', '<', '>', '?', '/', '\\', '{', '}', '[', ']', '|', ':', ';', ])) ->when($spaces, fn ($c) => $c->merge([' '])) ->pipe(fn ($c) => Collection::times($length, fn () => $c[random_int(0, $c->count() - 1)])) ->implode(''); } /** * Replace the first occurrence of the given value if it appears at the start of the string. * * @param string $search * @param string $replace * @param string $subject * @return string */ public static function replaceStart($search, $replace, $subject) { $search = (string) $search; if ($search === '') { return $subject; } if (static::startsWith($subject, $search)) { return static::replaceFirst($search, $replace, $subject); } return $subject; } /** * Replace the last occurrence of a given value if it appears at the end of the string. * * @param string $search * @param string $replace * @param string $subject * @return string */ public static function replaceEnd($search, $replace, $subject) { $search = (string) $search; if ($search === '') { return $subject; } if (static::endsWith($subject, $search)) { return static::replaceLast($search, $replace, $subject); } return $subject; } /** * Replace the patterns matching the given regular expression. * * @param string $pattern * @param Closure|string $replace * @param array|string $subject * @param int $limit * @return null|string|string[] */ public static function replaceMatches($pattern, $replace, $subject, $limit = -1) { if ($replace instanceof Closure) { return preg_replace_callback($pattern, $replace, $subject, $limit); } return preg_replace($pattern, $replace, $subject, $limit); } /** * @param string $value */ public static function reverse($value): string { return implode(array_reverse(mb_str_split($value))); } /** * Remove all whitespace from both ends of a string. * * @param string $value * @param null|string $charlist * @return string */ public static function trim($value, $charlist = null) { if ($charlist === null) { return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}]+|[\s\x{FEFF}\x{200B}\x{200E}]+$~u', '', $value) ?? trim($value); } return trim($value, $charlist); } /** * Remove all whitespace from the beginning of a string. * * @param string $value * @param null|string $charlist * @return string */ public static function ltrim($value, $charlist = null) { if ($charlist === null) { return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}]+~u', '', $value) ?? ltrim($value); } return ltrim($value, $charlist); } /** * Remove all whitespace from the end of a string. * * @param string $value * @param null|string $charlist * @return string */ public static function rtrim($value, $charlist = null) { if ($charlist === null) { return preg_replace('~[\s\x{FEFF}\x{200B}\x{200E}]+$~u', '', $value) ?? rtrim($value); } return rtrim($value, $charlist); } /** * Remove all "extra" blank space from the given string. * * @param string $value */ public static function squish($value): null|array|string { return preg_replace('~(\s|\x{3164}|\x{1160})+~u', ' ', static::trim($value)); } /** * Replace text within a portion of a string. * * @param string|string[] $string * @param string|string[] $replace * @param int|int[] $offset * @param null|int|int[] $length * @return string|string[] */ public static function substrReplace($string, $replace, $offset = 0, $length = null): array|string { if ($length === null) { $length = strlen($string); } return substr_replace($string, $replace, $offset, $length); } /** * Swap multiple keywords in a string with other keywords. * * @param string $subject * @return string */ public static function swap(array $map, $subject): array|string { return str_replace(array_keys($map), array_values($map), $subject); } /** * Take the first or last {$limit} characters of a string. */ public static function take(string $string, int $limit): string { if ($limit < 0) { return static::substr($string, $limit); } return static::substr($string, 0, $limit); } /** * Convert the given string to Base64 encoding. * * @param string $string */ public static function toBase64($string): string { return base64_encode($string); } /** * Split a string into pieces by uppercase characters. * * @param string $string * @return bool|string[] */ public static function ucsplit($string): array|bool { return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY); } /** * Unwrap the string with the given strings. * * @param string $value * @param string $before * @param null|string $after */ public static function unwrap($value, $before, $after = null): string { if (static::startsWith($value, $before)) { $value = static::substr($value, static::length($before)); } if (static::endsWith($value, $after ??= $before)) { $value = static::substr($value, 0, -static::length($after)); } return $value; } /** * Get the number of words a string contains. * * @param string $string */ public static function wordCount($string): array|int { return str_word_count($string); } /** * Wrap the string with the given strings. * * @param string $value * @param string $before * @param null|string $after */ public static function wrap($value, $before, $after = null): string { return $before . $value . ($after ??= $before); } /** * Wrap a string to a given number of characters. * * @param string $string * @param int $characters * @param string $break * @param bool $cutLongWords */ public static function wordWrap($string, $characters = 75, $break = "\n", $cutLongWords = false): string { return wordwrap($string, $characters, $break, $cutLongWords); } /** * Remove all non-numeric characters from a string. */ public static function numbers(array|string $value): array|string { return preg_replace('/[^0-9]/', '', $value); } /** * Decode the given Base64 encoded string. */ public static function fromBase64(string $string, bool $strict = false): false|string { return base64_decode($string, $strict); } /** * Returns the replacements for the ascii method. * Note: Adapted from Stringy\Stringy. * * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt */ protected static function charsArray(): array { static $charsArray; if (isset($charsArray)) { return $charsArray; } return $charsArray = [ '0' => ['°', '₀', '۰', '0'], '1' => ['¹', '₁', '۱', '1'], '2' => ['²', '₂', '۲', '2'], '3' => ['³', '₃', '۳', '3'], '4' => ['⁴', '₄', '۴', '٤', '4'], '5' => ['⁵', '₅', '۵', '٥', '5'], '6' => ['⁶', '₆', '۶', '٦', '6'], '7' => ['⁷', '₇', '۷', '7'], '8' => ['⁸', '₈', '۸', '8'], '9' => ['⁹', '₉', '۹', '9'], 'a' => [ 'à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä', ], 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], 'e' => [ 'é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e', ], 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'], 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], 'i' => [ 'í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i', ], 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'], 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'], 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'], 'o' => [ 'ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö', ], 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], 'q' => ['ყ', 'q'], 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'], 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'], 'u' => [ 'ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü', ], 'v' => ['в', 'ვ', 'ϐ', 'v'], 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], 'x' => ['χ', 'ξ', 'x'], 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], 'aa' => ['ع', 'आ', 'آ'], 'ae' => ['æ', 'ǽ'], 'ai' => ['ऐ'], 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], 'dj' => ['ђ', 'đ'], 'dz' => ['џ', 'ძ'], 'ei' => ['ऍ'], 'gh' => ['غ', 'ღ'], 'ii' => ['ई'], 'ij' => ['ij'], 'kh' => ['х', 'خ', 'ხ'], 'lj' => ['љ'], 'nj' => ['њ'], 'oe' => ['ö', 'œ', 'ؤ'], 'oi' => ['ऑ'], 'oii' => ['ऒ'], 'ps' => ['ψ'], 'sh' => ['ш', 'შ', 'ش'], 'shch' => ['щ'], 'ss' => ['ß'], 'sx' => ['ŝ'], 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], 'ts' => ['ц', 'ც', 'წ'], 'ue' => ['ü'], 'uu' => ['ऊ'], 'ya' => ['я'], 'yu' => ['ю'], 'zh' => ['ж', 'ჟ', 'ژ'], '(c)' => ['©'], 'A' => [ 'Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä', ], 'B' => ['Б', 'Β', 'ब', 'B'], 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'], 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'], 'E' => [ 'É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E', ], 'F' => ['Ф', 'Φ', 'F'], 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], 'H' => ['Η', 'Ή', 'Ħ', 'H'], 'I' => [ 'Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I', ], 'J' => ['J'], 'K' => ['К', 'Κ', 'K'], 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], 'M' => ['М', 'Μ', 'M'], 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], 'O' => [ 'Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö', ], 'P' => ['П', 'Π', 'P'], 'Q' => ['Q'], 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], 'U' => [ 'Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü', ], 'V' => ['В', 'V'], 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], 'X' => ['Χ', 'Ξ', 'X'], 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], 'AE' => ['Æ', 'Ǽ'], 'Ch' => ['Ч'], 'Dj' => ['Ђ'], 'Dz' => ['Џ'], 'Gx' => ['Ĝ'], 'Hx' => ['Ĥ'], 'Ij' => ['IJ'], 'Jx' => ['Ĵ'], 'Kh' => ['Х'], 'Lj' => ['Љ'], 'Nj' => ['Њ'], 'Oe' => ['Œ'], 'Ps' => ['Ψ'], 'Sh' => ['Ш'], 'Shch' => ['Щ'], 'Ss' => ['ẞ'], 'Th' => ['Þ'], 'Ts' => ['Ц'], 'Ya' => ['Я'], 'Yu' => ['Ю'], 'Zh' => ['Ж'], ' ' => [ "\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0", ], ]; } /** * Returns the language specific replacements for the ascii method. * Note: Adapted from Stringy\Stringy. * * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt * @return null|array */ protected static function languageSpecificCharsArray(string $language) { static $languageSpecific; if (! isset($languageSpecific)) { $languageSpecific = [ 'bg' => [ ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'], ], 'de' => [ ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], ], ]; } return $languageSpecific[$language] ?? null; } }