friendship ended with social-app. php is my new best friend
1<?php 2/** 3 * Class String 4 * 5 * @created 29.10.2024 6 * @author smiley <smiley@chillerlan.net> 7 * @copyright 2024 smiley 8 * @license MIT 9 */ 10declare(strict_types=1); 11 12namespace chillerlan\Utilities; 13 14use RuntimeException; 15use function array_filter; 16use function array_map; 17use function array_values; 18use function is_string; 19use function json_decode; 20use function json_encode; 21use function mb_strtolower; 22use function sodium_bin2base64; 23use function str_contains; 24use function str_replace; 25use function str_starts_with; 26use const JSON_PRETTY_PRINT; 27use const JSON_THROW_ON_ERROR; 28use const JSON_UNESCAPED_SLASHES; 29use const JSON_UNESCAPED_UNICODE; 30use const SODIUM_BASE64_VARIANT_ORIGINAL; 31 32/** 33 * string handling helpers 34 */ 35final class Str{ 36 37 public const JSON_ENCODE_FLAGS_DEFAULT = (JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); 38 39 /** 40 * Filters an array and removes all elements that are not strings. Array keys are *not* retained. 41 * 42 * @see array_filter() 43 * @see array_values() 44 * @see is_string() 45 * 46 * @param array<string|int, mixed> $mixed 47 * @return string[] 48 */ 49 public static function filter(array $mixed):array{ 50 return array_filter(array_values($mixed), is_string(...)); 51 } 52 53 /** 54 * Converts the strings in an array to uppercase 55 * 56 * @see mb_strtoupper() 57 * @see self::filter() 58 * 59 * @param string[] $strings 60 * @return string[] 61 * 62 * @codeCoverageIgnore 63 */ 64 public static function toUpper(array $strings):array{ 65 return array_map(mb_strtoupper(...), self::filter($strings)); 66 } 67 68 /** 69 * Converts the strings in an array to lowercase 70 * 71 * @see mb_strtolower() 72 * @see self::filter() 73 * 74 * @param string[] $strings 75 * @return string[] 76 * 77 * @codeCoverageIgnore 78 */ 79 public static function toLower(array $strings):array{ 80 return array_map(mb_strtolower(...), self::filter($strings)); 81 } 82 83 /** 84 * Checks whether the given string starts with *any* of the given array of needles. 85 * 86 * @see \str_starts_with() 87 * 88 * @param string[] $needles 89 */ 90 public static function startsWith(string $haystack, array $needles, bool $ignoreCase = false):bool{ 91 $needles = self::filter($needles); 92 93 if($needles === []){ 94 return true; 95 } 96 97 if($ignoreCase){ 98 $haystack = mb_strtolower($haystack); 99 $needles = array_map(mb_strtolower(...), $needles); 100 } 101 102 foreach($needles as $needle){ 103 if($needle !== '' && str_starts_with($haystack, $needle)){ 104 return true; 105 } 106 } 107 108 return false; 109 } 110 111 /** 112 * Checks whether the given string (haystack) contains *all* of the given array of needles. 113 * The given array is filtered for string values. 114 * 115 * @see \str_contains() 116 * 117 * @param string[] $needles 118 */ 119 public static function containsAll(string $haystack, array $needles, bool $ignoreCase = false):bool{ 120 $needles = self::filter($needles); 121 122 if($needles === []){ 123 return true; 124 } 125 126 if($ignoreCase){ 127 $haystack = mb_strtolower($haystack); 128 $needles = array_map(mb_strtolower(...), $needles); 129 } 130 131 foreach($needles as $needle){ 132 if($needle !== '' && !str_contains($haystack, $needle)){ 133 return false; 134 } 135 } 136 137 return true; 138 } 139 140 /** 141 * Checks whether the given string (haystack) contains *any* of the given array of needles. 142 * The given array is filtered for string values. 143 * 144 * @param string[] $needles 145 */ 146 public static function containsAny(string $haystack, array $needles, bool $ignoreCase = false):bool{ 147 $needles = self::filter($needles); 148 149 if($needles === []){ 150 return true; 151 } 152 153 if($ignoreCase){ 154 $haystack = mb_strtolower($haystack); 155 $needles = array_map(mb_strtolower(...), $needles); 156 } 157 158 return str_replace($needles, '', $haystack) !== $haystack; 159 } 160 161 /** 162 * Decodes a JSON string 163 * 164 * @throws \JsonException 165 * @codeCoverageIgnore 166 */ 167 public static function jsonDecode(string $json, bool $associative = false, int $flags = 0):mixed{ 168 $flags |= JSON_THROW_ON_ERROR; 169 170 return json_decode(json: $json, associative: $associative, flags: $flags); 171 } 172 173 /** 174 * Encodes a value into a JSON representation 175 * 176 * @throws \JsonException 177 * @codeCoverageIgnore 178 */ 179 public static function jsonEncode(mixed $data, int $flags = self::JSON_ENCODE_FLAGS_DEFAULT):string{ 180 $flags |= JSON_THROW_ON_ERROR; 181 182 $encoded = json_encode($data, $flags); 183 184 // the chance to run into this is near zero but hey, at least phpstan is happy 185 if($encoded === false){ 186 throw new RuntimeException('json_encode() error'); // @codeCoverageIgnore 187 } 188 189 return $encoded; 190 } 191 192 /** 193 * Encodes a binary string to base64 (timing-safe) 194 * 195 * @see sodium_bin2base64() 196 * 197 * @throws \SodiumException 198 * @codeCoverageIgnore 199 */ 200 public static function base64encode(string $string, int $variant = SODIUM_BASE64_VARIANT_ORIGINAL):string{ 201 return sodium_bin2base64($string, $variant); 202 } 203 204 /** 205 * Decodes a base64 string into binary (timing-safe) 206 * 207 * @see sodium_base642bin() 208 * 209 * @throws \SodiumException 210 * @codeCoverageIgnore 211 */ 212 public static function base64decode( 213 string $base64, 214 int $variant = SODIUM_BASE64_VARIANT_ORIGINAL, 215 string $ignore = '', 216 ):string{ 217 return sodium_base642bin($base64, $variant, $ignore); 218 } 219 220}