1<?php 2namespace Aws; 3 4use GuzzleHttp\Client; 5use Psr\Http\Message\RequestInterface; 6use GuzzleHttp\ClientInterface; 7use GuzzleHttp\Promise\FulfilledPromise; 8 9//----------------------------------------------------------------------------- 10// Functional functions 11//----------------------------------------------------------------------------- 12 13/** 14 * Returns a function that always returns the same value; 15 * 16 * @param mixed $value Value to return. 17 * 18 * @return callable 19 */ 20function constantly($value) 21{ 22 return function () use ($value) { return $value; }; 23} 24 25/** 26 * Filters values that do not satisfy the predicate function $pred. 27 * 28 * @param mixed $iterable Iterable sequence of data. 29 * @param callable $pred Function that accepts a value and returns true/false 30 * 31 * @return \Generator 32 */ 33function filter($iterable, callable $pred) 34{ 35 foreach ($iterable as $value) { 36 if ($pred($value)) { 37 yield $value; 38 } 39 } 40} 41 42/** 43 * Applies a map function $f to each value in a collection. 44 * 45 * @param mixed $iterable Iterable sequence of data. 46 * @param callable $f Map function to apply. 47 * 48 * @return \Generator 49 */ 50function map($iterable, callable $f) 51{ 52 foreach ($iterable as $value) { 53 yield $f($value); 54 } 55} 56 57/** 58 * Creates a generator that iterates over a sequence, then iterates over each 59 * value in the sequence and yields the application of the map function to each 60 * value. 61 * 62 * @param mixed $iterable Iterable sequence of data. 63 * @param callable $f Map function to apply. 64 * 65 * @return \Generator 66 */ 67function flatmap($iterable, callable $f) 68{ 69 foreach (map($iterable, $f) as $outer) { 70 foreach ($outer as $inner) { 71 yield $inner; 72 } 73 } 74} 75 76/** 77 * Partitions the input sequence into partitions of the specified size. 78 * 79 * @param mixed $iterable Iterable sequence of data. 80 * @param int $size Size to make each partition (except possibly the last chunk) 81 * 82 * @return \Generator 83 */ 84function partition($iterable, $size) 85{ 86 $buffer = []; 87 foreach ($iterable as $value) { 88 $buffer[] = $value; 89 if (count($buffer) === $size) { 90 yield $buffer; 91 $buffer = []; 92 } 93 } 94 95 if ($buffer) { 96 yield $buffer; 97 } 98} 99 100/** 101 * Returns a function that invokes the provided variadic functions one 102 * after the other until one of the functions returns a non-null value. 103 * The return function will call each passed function with any arguments it 104 * is provided. 105 * 106 * $a = function ($x, $y) { return null; }; 107 * $b = function ($x, $y) { return $x + $y; }; 108 * $fn = \Aws\or_chain($a, $b); 109 * echo $fn(1, 2); // 3 110 * 111 * @return callable 112 */ 113function or_chain() 114{ 115 $fns = func_get_args(); 116 return function () use ($fns) { 117 $args = func_get_args(); 118 foreach ($fns as $fn) { 119 $result = $args ? call_user_func_array($fn, $args) : $fn(); 120 if ($result) { 121 return $result; 122 } 123 } 124 return null; 125 }; 126} 127 128//----------------------------------------------------------------------------- 129// JSON compiler and loading functions 130//----------------------------------------------------------------------------- 131 132/** 133 * Loads a compiled JSON file from a PHP file. 134 * 135 * If the JSON file has not been cached to disk as a PHP file, it will be loaded 136 * from the JSON source file and returned. 137 * 138 * @param string $path Path to the JSON file on disk 139 * 140 * @return mixed Returns the JSON decoded data. Note that JSON objects are 141 * decoded as associative arrays. 142 */ 143function load_compiled_json($path) 144{ 145 static $compiledList = []; 146 147 $compiledFilepath = "{$path}.php"; 148 149 if (!isset($compiledList[$compiledFilepath])) { 150 if (is_readable($compiledFilepath)) { 151 $compiledList[$compiledFilepath] = include($compiledFilepath); 152 } 153 } 154 155 if (isset($compiledList[$compiledFilepath])) { 156 return $compiledList[$compiledFilepath]; 157 } 158 159 if (!file_exists($path)) { 160 throw new \InvalidArgumentException( 161 sprintf("File not found: %s", $path) 162 ); 163 } 164 165 return json_decode(file_get_contents($path), true); 166} 167 168/** 169 * No-op 170 */ 171function clear_compiled_json() 172{ 173 // pass 174} 175 176//----------------------------------------------------------------------------- 177// Directory iterator functions. 178//----------------------------------------------------------------------------- 179 180/** 181 * Iterates over the files in a directory and works with custom wrappers. 182 * 183 * @param string $path Path to open (e.g., "s3://foo/bar"). 184 * @param resource $context Stream wrapper context. 185 * 186 * @return \Generator Yields relative filename strings. 187 */ 188function dir_iterator($path, $context = null) 189{ 190 $dh = $context ? opendir($path, $context) : opendir($path); 191 if (!$dh) { 192 throw new \InvalidArgumentException('File not found: ' . $path); 193 } 194 while (($file = readdir($dh)) !== false) { 195 yield $file; 196 } 197 closedir($dh); 198} 199 200/** 201 * Returns a recursive directory iterator that yields absolute filenames. 202 * 203 * This iterator is not broken like PHP's built-in DirectoryIterator (which 204 * will read the first file from a stream wrapper, then rewind, then read 205 * it again). 206 * 207 * @param string $path Path to traverse (e.g., s3://bucket/key, /tmp) 208 * @param resource $context Stream context options. 209 * 210 * @return \Generator Yields absolute filenames. 211 */ 212function recursive_dir_iterator($path, $context = null) 213{ 214 $invalid = ['.' => true, '..' => true]; 215 $pathLen = strlen($path) + 1; 216 $iterator = dir_iterator($path, $context); 217 $queue = []; 218 do { 219 while ($iterator->valid()) { 220 $file = $iterator->current(); 221 $iterator->next(); 222 if (isset($invalid[basename($file)])) { 223 continue; 224 } 225 $fullPath = "{$path}/{$file}"; 226 yield $fullPath; 227 if (is_dir($fullPath)) { 228 $queue[] = $iterator; 229 $iterator = map( 230 dir_iterator($fullPath, $context), 231 function ($file) use ($fullPath, $pathLen) { 232 return substr("{$fullPath}/{$file}", $pathLen); 233 } 234 ); 235 continue; 236 } 237 } 238 $iterator = array_pop($queue); 239 } while ($iterator); 240} 241 242//----------------------------------------------------------------------------- 243// Misc. functions. 244//----------------------------------------------------------------------------- 245 246/** 247 * Debug function used to describe the provided value type and class. 248 * 249 * @param mixed $input 250 * 251 * @return string Returns a string containing the type of the variable and 252 * if a class is provided, the class name. 253 */ 254function describe_type($input) 255{ 256 switch (gettype($input)) { 257 case 'object': 258 return 'object(' . get_class($input) . ')'; 259 case 'array': 260 return 'array(' . count($input) . ')'; 261 default: 262 ob_start(); 263 var_dump($input); 264 // normalize float vs double 265 return str_replace('double(', 'float(', rtrim(ob_get_clean())); 266 } 267} 268 269/** 270 * Creates a default HTTP handler based on the available clients. 271 * 272 * @return callable 273 */ 274function default_http_handler() 275{ 276 $version = guzzle_major_version(); 277 // If Guzzle 6 or 7 installed 278 if ($version === 6 || $version === 7) { 279 return new \Aws\Handler\GuzzleV6\GuzzleHandler(); 280 } 281 282 // If Guzzle 5 installed 283 if ($version === 5) { 284 return new \Aws\Handler\GuzzleV5\GuzzleHandler(); 285 } 286 287 throw new \RuntimeException('Unknown Guzzle version: ' . $version); 288} 289 290/** 291 * Gets the default user agent string depending on the Guzzle version 292 * 293 * @return string 294 */ 295function default_user_agent() 296{ 297 $version = guzzle_major_version(); 298 // If Guzzle 6 or 7 installed 299 if ($version === 6 || $version === 7) { 300 return \GuzzleHttp\default_user_agent(); 301 } 302 303 // If Guzzle 5 installed 304 if ($version === 5) { 305 return \GuzzleHttp\Client::getDefaultUserAgent(); 306 } 307 308 throw new \RuntimeException('Unknown Guzzle version: ' . $version); 309} 310 311/** 312 * Get the major version of guzzle that is installed. 313 * 314 * @internal This function is internal and should not be used outside aws/aws-sdk-php. 315 * @return int 316 * @throws \RuntimeException 317 */ 318function guzzle_major_version() 319{ 320 static $cache = null; 321 if (null !== $cache) { 322 return $cache; 323 } 324 325 if (defined('\GuzzleHttp\ClientInterface::VERSION')) { 326 $version = (string) ClientInterface::VERSION; 327 if ($version[0] === '6') { 328 return $cache = 6; 329 } 330 if ($version[0] === '5') { 331 return $cache = 5; 332 } 333 } elseif (defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION')) { 334 return $cache = ClientInterface::MAJOR_VERSION; 335 } 336 337 throw new \RuntimeException('Unable to determine what Guzzle version is installed.'); 338} 339 340/** 341 * Serialize a request for a command but do not send it. 342 * 343 * Returns a promise that is fulfilled with the serialized request. 344 * 345 * @param CommandInterface $command Command to serialize. 346 * 347 * @return RequestInterface 348 * @throws \RuntimeException 349 */ 350function serialize(CommandInterface $command) 351{ 352 $request = null; 353 $handlerList = $command->getHandlerList(); 354 355 // Return a mock result. 356 $handlerList->setHandler( 357 function (CommandInterface $_, RequestInterface $r) use (&$request) { 358 $request = $r; 359 return new FulfilledPromise(new Result([])); 360 } 361 ); 362 363 call_user_func($handlerList->resolve(), $command)->wait(); 364 if (!$request instanceof RequestInterface) { 365 throw new \RuntimeException( 366 'Calling handler did not serialize request' 367 ); 368 } 369 370 return $request; 371} 372 373/** 374 * Retrieves data for a service from the SDK's service manifest file. 375 * 376 * Manifest data is stored statically, so it does not need to be loaded more 377 * than once per process. The JSON data is also cached in opcache. 378 * 379 * @param string $service Case-insensitive namespace or endpoint prefix of the 380 * service for which you are retrieving manifest data. 381 * 382 * @return array 383 * @throws \InvalidArgumentException if the service is not supported. 384 */ 385function manifest($service = null) 386{ 387 // Load the manifest and create aliases for lowercased namespaces 388 static $manifest = []; 389 static $aliases = []; 390 if (empty($manifest)) { 391 $manifest = load_compiled_json(__DIR__ . '/data/manifest.json'); 392 foreach ($manifest as $endpoint => $info) { 393 $alias = strtolower($info['namespace']); 394 if ($alias !== $endpoint) { 395 $aliases[$alias] = $endpoint; 396 } 397 } 398 } 399 400 // If no service specified, then return the whole manifest. 401 if ($service === null) { 402 return $manifest; 403 } 404 405 // Look up the service's info in the manifest data. 406 $service = strtolower($service); 407 if (isset($manifest[$service])) { 408 return $manifest[$service] + ['endpoint' => $service]; 409 } 410 411 if (isset($aliases[$service])) { 412 return manifest($aliases[$service]); 413 } 414 415 throw new \InvalidArgumentException( 416 "The service \"{$service}\" is not provided by the AWS SDK for PHP." 417 ); 418} 419 420/** 421 * Checks if supplied parameter is a valid hostname 422 * 423 * @param string $hostname 424 * @return bool 425 */ 426function is_valid_hostname($hostname) 427{ 428 return ( 429 preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*\.?$/i", $hostname) 430 && preg_match("/^.{1,253}$/", $hostname) 431 && preg_match("/^[^\.]{1,63}(\.[^\.]{0,63})*$/", $hostname) 432 ); 433} 434 435/** 436 * Checks if supplied parameter is a valid host label 437 * 438 * @param $label 439 * @return bool 440 */ 441function is_valid_hostlabel($label) 442{ 443 return preg_match("/^(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$/", $label); 444} 445 446/** 447 * Ignores '#' full line comments, which parse_ini_file no longer does 448 * in PHP 7+. 449 * 450 * @param $filename 451 * @param bool $process_sections 452 * @param int $scanner_mode 453 * @return array|bool 454 */ 455function parse_ini_file( 456 $filename, 457 $process_sections = false, 458 $scanner_mode = INI_SCANNER_NORMAL) 459{ 460 return parse_ini_string( 461 preg_replace('/^#.*\\n/m', "", file_get_contents($filename)), 462 $process_sections, 463 $scanner_mode 464 ); 465} 466 467/** 468 * Outputs boolean value of input for a select range of possible values, 469 * null otherwise 470 * 471 * @param $input 472 * @return bool|null 473 */ 474function boolean_value($input) 475{ 476 if (is_bool($input)) { 477 return $input; 478 } 479 480 if ($input === 0) { 481 return false; 482 } 483 484 if ($input === 1) { 485 return true; 486 } 487 488 if (is_string($input)) { 489 switch (strtolower($input)) { 490 case "true": 491 case "on": 492 case "1": 493 return true; 494 break; 495 496 case "false": 497 case "off": 498 case "0": 499 return false; 500 break; 501 } 502 } 503 return null; 504} 505 506/** 507 * Checks if an input is a valid epoch time 508 * 509 * @param $input 510 * @return bool 511 */ 512function is_valid_epoch($input) 513{ 514 if (is_string($input) || is_numeric($input)) { 515 if (is_string($input) && !preg_match("/^-?[0-9]+\.?[0-9]*$/", $input)) { 516 return false; 517 } 518 return true; 519 } 520 return false; 521} 522