PKcRZ^>'Whoops/Inspector/InspectorInterface.phpnu[ */ namespace Whoops\Inspector; interface InspectorInterface { /** * @return \Throwable */ public function getException(); /** * @return string */ public function getExceptionName(); /** * @return string */ public function getExceptionMessage(); /** * @return string[] */ public function getPreviousExceptionMessages(); /** * @return int[] */ public function getPreviousExceptionCodes(); /** * Returns a url to the php-manual related to the underlying error - when available. * * @return string|null */ public function getExceptionDocrefUrl(); /** * Does the wrapped Exception has a previous Exception? * @return bool */ public function hasPreviousException(); /** * Returns an Inspector for a previous Exception, if any. * @todo Clean this up a bit, cache stuff a bit better. * @return InspectorInterface */ public function getPreviousExceptionInspector(); /** * Returns an array of all previous exceptions for this inspector's exception * @return \Throwable[] */ public function getPreviousExceptions(); /** * Returns an iterator for the inspected exception's * frames. * * @param array $frameFilters * * @return \Whoops\Exception\FrameCollection */ public function getFrames(array $frameFilters = []); } PKcRZe5++.Whoops/Inspector/InspectorFactoryInterface.phpnu[ */ namespace Whoops\Inspector; interface InspectorFactoryInterface { /** * @param \Throwable $exception * @return InspectorInterface */ public function create($exception); } PKcRZeY%Whoops/Inspector/InspectorFactory.phpnu[ */ namespace Whoops\Inspector; use Whoops\Exception\Inspector; class InspectorFactory implements InspectorFactoryInterface { /** * @param \Throwable $exception * @return InspectorInterface */ public function create($exception) { return new Inspector($exception, $this); } } PKcRZWhoops/Util/Misc.phpnu[ */ namespace Whoops\Util; class Misc { /** * Can we at this point in time send HTTP headers? * * Currently this checks if we are even serving an HTTP request, * as opposed to running from a command line. * * If we are serving an HTTP request, we check if it's not too late. * * @return bool */ public static function canSendHeaders() { return isset($_SERVER["REQUEST_URI"]) && !headers_sent(); } public static function isAjaxRequest() { return ( !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'); } /** * Check, if possible, that this execution was triggered by a command line. * @return bool */ public static function isCommandLine() { return PHP_SAPI == 'cli'; } /** * Translate ErrorException code into the represented constant. * * @param int $error_code * @return string */ public static function translateErrorCode($error_code) { $constants = get_defined_constants(true); if (array_key_exists('Core', $constants)) { foreach ($constants['Core'] as $constant => $value) { if (substr($constant, 0, 2) == 'E_' && $value == $error_code) { return $constant; } } } return "E_UNKNOWN"; } /** * Determine if an error level is fatal (halts execution) * * @param int $level * @return bool */ public static function isLevelFatal($level) { $errors = E_ERROR; $errors |= E_PARSE; $errors |= E_CORE_ERROR; $errors |= E_CORE_WARNING; $errors |= E_COMPILE_ERROR; $errors |= E_COMPILE_WARNING; return ($level & $errors) > 0; } } PKcRZJ Whoops/Util/SystemFacade.phpnu[ */ namespace Whoops\Util; class SystemFacade { /** * Turns on output buffering. * * @return bool */ public function startOutputBuffering() { return ob_start(); } /** * @param callable $handler * @param int $types * * @return callable|null */ public function setErrorHandler(callable $handler, $types = 'use-php-defaults') { // Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT) if ($types === 'use-php-defaults') { $types = E_ALL; } return set_error_handler($handler, $types); } /** * @param callable $handler * * @return callable|null */ public function setExceptionHandler(callable $handler) { return set_exception_handler($handler); } /** * @return void */ public function restoreExceptionHandler() { restore_exception_handler(); } /** * @return void */ public function restoreErrorHandler() { restore_error_handler(); } /** * @param callable $function * * @return void */ public function registerShutdownFunction(callable $function) { register_shutdown_function($function); } /** * @return string|false */ public function cleanOutputBuffer() { return ob_get_clean(); } /** * @return int */ public function getOutputBufferLevel() { return ob_get_level(); } /** * @return bool */ public function endOutputBuffering() { return ob_end_clean(); } /** * @return void */ public function flushOutputBuffer() { flush(); } /** * @return int */ public function getErrorReportingLevel() { return error_reporting(); } /** * @return array|null */ public function getLastError() { return error_get_last(); } /** * @param int $httpCode * * @return int */ public function setHttpResponseCode($httpCode) { if (!headers_sent()) { // Ensure that no 'location' header is present as otherwise this // will override the HTTP code being set here, and mask the // expected error page. header_remove('location'); } return http_response_code($httpCode); } /** * @param int $exitStatus */ public function stopExecution($exitStatus) { exit($exitStatus); } } PKcRZZ Whoops/Util/HtmlDumperOutput.phpnu[ */ namespace Whoops\Util; /** * Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump() * * @see TemplateHelper::dump() */ class HtmlDumperOutput { private $output; public function __invoke($line, $depth) { // A negative depth means "end of dump" if ($depth >= 0) { // Adds a two spaces indentation to the line $this->output .= str_repeat(' ', $depth) . $line . "\n"; } } public function getOutput() { return $this->output; } public function clear() { $this->output = null; } } PKcRZM%%Whoops/Util/TemplateHelper.phpnu[ */ namespace Whoops\Util; use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Cloner\AbstractCloner; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Whoops\Exception\Frame; /** * Exposes useful tools for working with/in templates */ class TemplateHelper { /** * An array of variables to be passed to all templates * @var array */ private $variables = []; /** * @var HtmlDumper */ private $htmlDumper; /** * @var HtmlDumperOutput */ private $htmlDumperOutput; /** * @var AbstractCloner */ private $cloner; /** * @var string */ private $applicationRootPath; public function __construct() { // root path for ordinary composer projects $this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); } /** * Escapes a string for output in an HTML document * * @param string $raw * @return string */ public function escape($raw) { $flags = ENT_QUOTES; // HHVM has all constants defined, but only ENT_IGNORE // works at the moment if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) { $flags |= ENT_SUBSTITUTE; } else { // This is for 5.3. // The documentation warns of a potential security issue, // but it seems it does not apply in our case, because // we do not blacklist anything anywhere. $flags |= ENT_IGNORE; } $raw = str_replace(chr(9), ' ', $raw); return htmlspecialchars($raw, $flags, "UTF-8"); } /** * Escapes a string for output in an HTML document, but preserves * URIs within it, and converts them to clickable anchor elements. * * @param string $raw * @return string */ public function escapeButPreserveUris($raw) { $escaped = $this->escape($raw); return preg_replace( "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@", "$1", $escaped ); } /** * Makes sure that the given string breaks on the delimiter. * * @param string $delimiter * @param string $s * @return string */ public function breakOnDelimiter($delimiter, $s) { $parts = explode($delimiter, $s); foreach ($parts as &$part) { $part = '' . $part . ''; } return implode($delimiter, $parts); } /** * Replace the part of the path that all files have in common. * * @param string $path * @return string */ public function shorten($path) { if ($this->applicationRootPath != "/") { $path = str_replace($this->applicationRootPath, '…', $path); } return $path; } private function getDumper() { if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) { $this->htmlDumperOutput = new HtmlDumperOutput(); // re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump. $this->htmlDumper = new HtmlDumper($this->htmlDumperOutput); $styles = [ 'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal', 'num' => 'color:#BCD42A', 'const' => 'color: #4bb1b1;', 'str' => 'color:#BCD42A', 'note' => 'color:#ef7c61', 'ref' => 'color:#A0A0A0', 'public' => 'color:#FFFFFF', 'protected' => 'color:#FFFFFF', 'private' => 'color:#FFFFFF', 'meta' => 'color:#FFFFFF', 'key' => 'color:#BCD42A', 'index' => 'color:#ef7c61', ]; $this->htmlDumper->setStyles($styles); } return $this->htmlDumper; } /** * Format the given value into a human readable string. * * @param mixed $value * @return string */ public function dump($value) { $dumper = $this->getDumper(); if ($dumper) { // re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump. // exclude verbose information (e.g. exception stack traces) if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) { $cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE); // Symfony VarDumper 2.6 Caster class dont exist. } else { $cloneVar = $this->getCloner()->cloneVar($value); } $dumper->dump( $cloneVar, $this->htmlDumperOutput ); $output = $this->htmlDumperOutput->getOutput(); $this->htmlDumperOutput->clear(); return $output; } return htmlspecialchars(print_r($value, true)); } /** * Format the args of the given Frame as a human readable html string * * @param Frame $frame * @return string the rendered html */ public function dumpArgs(Frame $frame) { // we support frame args only when the optional dumper is available if (!$this->getDumper()) { return ''; } $html = ''; $numFrames = count($frame->getArgs()); if ($numFrames > 0) { $html = '
    '; foreach ($frame->getArgs() as $j => $frameArg) { $html .= '
  1. '. $this->dump($frameArg) .'
  2. '; } $html .= '
'; } return $html; } /** * Convert a string to a slug version of itself * * @param string $original * @return string */ public function slug($original) { $slug = str_replace(" ", "-", $original); $slug = preg_replace('/[^\w\d\-\_]/i', '', $slug); return strtolower($slug); } /** * Given a template path, render it within its own scope. This * method also accepts an array of additional variables to be * passed to the template. * * @param string $template */ public function render($template, array $additionalVariables = null) { $variables = $this->getVariables(); // Pass the helper to the template: $variables["tpl"] = $this; if ($additionalVariables !== null) { $variables = array_replace($variables, $additionalVariables); } call_user_func(function () { extract(func_get_arg(1)); require func_get_arg(0); }, $template, $variables); } /** * Sets the variables to be passed to all templates rendered * by this template helper. */ public function setVariables(array $variables) { $this->variables = $variables; } /** * Sets a single template variable, by its name: * * @param string $variableName * @param mixed $variableValue */ public function setVariable($variableName, $variableValue) { $this->variables[$variableName] = $variableValue; } /** * Gets a single template variable, by its name, or * $defaultValue if the variable does not exist * * @param string $variableName * @param mixed $defaultValue * @return mixed */ public function getVariable($variableName, $defaultValue = null) { return isset($this->variables[$variableName]) ? $this->variables[$variableName] : $defaultValue; } /** * Unsets a single template variable, by its name * * @param string $variableName */ public function delVariable($variableName) { unset($this->variables[$variableName]); } /** * Returns all variables for this helper * * @return array */ public function getVariables() { return $this->variables; } /** * Set the cloner used for dumping variables. * * @param AbstractCloner $cloner */ public function setCloner($cloner) { $this->cloner = $cloner; } /** * Get the cloner used for dumping variables. * * @return AbstractCloner */ public function getCloner() { if (!$this->cloner) { $this->cloner = new VarCloner(); } return $this->cloner; } /** * Set the application root path. * * @param string $applicationRootPath */ public function setApplicationRootPath($applicationRootPath) { $this->applicationRootPath = $applicationRootPath; } /** * Return the application root path. * * @return string */ public function getApplicationRootPath() { return $this->applicationRootPath; } } PKcRZ ))$Whoops/Exception/FrameCollection.phpnu[ */ namespace Whoops\Exception; use ArrayAccess; use ArrayIterator; use Countable; use IteratorAggregate; use ReturnTypeWillChange; use Serializable; use UnexpectedValueException; /** * Exposes a fluent interface for dealing with an ordered list * of stack-trace frames. */ class FrameCollection implements ArrayAccess, IteratorAggregate, Serializable, Countable { /** * @var array[] */ private $frames; public function __construct(array $frames) { $this->frames = array_map(function ($frame) { return new Frame($frame); }, $frames); } /** * Filters frames using a callable, returns the same FrameCollection * * @param callable $callable * @return FrameCollection */ public function filter($callable) { $this->frames = array_values(array_filter($this->frames, $callable)); return $this; } /** * Map the collection of frames * * @param callable $callable * @return FrameCollection */ public function map($callable) { // Contain the map within a higher-order callable // that enforces type-correctness for the $callable $this->frames = array_map(function ($frame) use ($callable) { $frame = call_user_func($callable, $frame); if (!$frame instanceof Frame) { throw new UnexpectedValueException( "Callable to " . __CLASS__ . "::map must return a Frame object" ); } return $frame; }, $this->frames); return $this; } /** * Returns an array with all frames, does not affect * the internal array. * * @todo If this gets any more complex than this, * have getIterator use this method. * @see FrameCollection::getIterator * @return array */ public function getArray() { return $this->frames; } /** * @see IteratorAggregate::getIterator * @return ArrayIterator */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->frames); } /** * @see ArrayAccess::offsetExists * @param int $offset */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->frames[$offset]); } /** * @see ArrayAccess::offsetGet * @param int $offset */ #[ReturnTypeWillChange] public function offsetGet($offset) { return $this->frames[$offset]; } /** * @see ArrayAccess::offsetSet * @param int $offset */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { throw new \Exception(__CLASS__ . ' is read only'); } /** * @see ArrayAccess::offsetUnset * @param int $offset */ #[ReturnTypeWillChange] public function offsetUnset($offset) { throw new \Exception(__CLASS__ . ' is read only'); } /** * @see Countable::count * @return int */ #[ReturnTypeWillChange] public function count() { return count($this->frames); } /** * Count the frames that belongs to the application. * * @return int */ public function countIsApplication() { return count(array_filter($this->frames, function (Frame $f) { return $f->isApplication(); })); } /** * @see Serializable::serialize * @return string */ #[ReturnTypeWillChange] public function serialize() { return serialize($this->frames); } /** * @see Serializable::unserialize * @param string $serializedFrames */ #[ReturnTypeWillChange] public function unserialize($serializedFrames) { $this->frames = unserialize($serializedFrames); } public function __serialize() { return $this->frames; } public function __unserialize(array $serializedFrames) { $this->frames = $serializedFrames; } /** * @param Frame[] $frames Array of Frame instances, usually from $e->getPrevious() */ public function prependFrames(array $frames) { $this->frames = array_merge($frames, $this->frames); } /** * Gets the innermost part of stack trace that is not the same as that of outer exception * * @param FrameCollection $parentFrames Outer exception frames to compare tail against * @return Frame[] */ public function topDiff(FrameCollection $parentFrames) { $diff = $this->frames; $parentFrames = $parentFrames->getArray(); $p = count($parentFrames)-1; for ($i = count($diff)-1; $i >= 0 && $p >= 0; $i--) { /** @var Frame $tailFrame */ $tailFrame = $diff[$i]; if ($tailFrame->equals($parentFrames[$p])) { unset($diff[$i]); } $p--; } return $diff; } } PKcRZ.%%Whoops/Exception/Inspector.phpnu[ */ namespace Whoops\Exception; use Whoops\Inspector\InspectorFactory; use Whoops\Inspector\InspectorInterface; use Whoops\Util\Misc; class Inspector implements InspectorInterface { /** * @var \Throwable */ private $exception; /** * @var \Whoops\Exception\FrameCollection */ private $frames; /** * @var \Whoops\Exception\Inspector */ private $previousExceptionInspector; /** * @var \Throwable[] */ private $previousExceptions; /** * @var \Whoops\Inspector\InspectorFactoryInterface|null */ protected $inspectorFactory; /** * @param \Throwable $exception The exception to inspect * @param \Whoops\Inspector\InspectorFactoryInterface $factory */ public function __construct($exception, $factory = null) { $this->exception = $exception; $this->inspectorFactory = $factory ?: new InspectorFactory(); } /** * @return \Throwable */ public function getException() { return $this->exception; } /** * @return string */ public function getExceptionName() { return get_class($this->exception); } /** * @return string */ public function getExceptionMessage() { return $this->extractDocrefUrl($this->exception->getMessage())['message']; } /** * @return string[] */ public function getPreviousExceptionMessages() { return array_map(function ($prev) { /** @var \Throwable $prev */ return $this->extractDocrefUrl($prev->getMessage())['message']; }, $this->getPreviousExceptions()); } /** * @return int[] */ public function getPreviousExceptionCodes() { return array_map(function ($prev) { /** @var \Throwable $prev */ return $prev->getCode(); }, $this->getPreviousExceptions()); } /** * Returns a url to the php-manual related to the underlying error - when available. * * @return string|null */ public function getExceptionDocrefUrl() { return $this->extractDocrefUrl($this->exception->getMessage())['url']; } private function extractDocrefUrl($message) { $docref = [ 'message' => $message, 'url' => null, ]; // php embbeds urls to the manual into the Exception message with the following ini-settings defined // http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root if (!ini_get('html_errors') || !ini_get('docref_root')) { return $docref; } $pattern = "/\[(?:[^<]+)<\/a>\]/"; if (preg_match($pattern, $message, $matches)) { // -> strip those automatically generated links from the exception message $docref['message'] = preg_replace($pattern, '', $message, 1); $docref['url'] = $matches[1]; } return $docref; } /** * Does the wrapped Exception has a previous Exception? * @return bool */ public function hasPreviousException() { return $this->previousExceptionInspector || $this->exception->getPrevious(); } /** * Returns an Inspector for a previous Exception, if any. * @todo Clean this up a bit, cache stuff a bit better. * @return Inspector */ public function getPreviousExceptionInspector() { if ($this->previousExceptionInspector === null) { $previousException = $this->exception->getPrevious(); if ($previousException) { $this->previousExceptionInspector = $this->inspectorFactory->create($previousException); } } return $this->previousExceptionInspector; } /** * Returns an array of all previous exceptions for this inspector's exception * @return \Throwable[] */ public function getPreviousExceptions() { if ($this->previousExceptions === null) { $this->previousExceptions = []; $prev = $this->exception->getPrevious(); while ($prev !== null) { $this->previousExceptions[] = $prev; $prev = $prev->getPrevious(); } } return $this->previousExceptions; } /** * Returns an iterator for the inspected exception's * frames. * * @param array $frameFilters * * @return \Whoops\Exception\FrameCollection */ public function getFrames(array $frameFilters = []) { if ($this->frames === null) { $frames = $this->getTrace($this->exception); // Fill empty line/file info for call_user_func_array usages (PHP Bug #44428) foreach ($frames as $k => $frame) { if (empty($frame['file'])) { // Default values when file and line are missing $file = '[internal]'; $line = 0; $next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : []; if ($this->isValidNextFrame($next_frame)) { $file = $next_frame['file']; $line = $next_frame['line']; } $frames[$k]['file'] = $file; $frames[$k]['line'] = $line; } } // Find latest non-error handling frame index ($i) used to remove error handling frames $i = 0; foreach ($frames as $k => $frame) { if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) { $i = $k; } } // Remove error handling frames if ($i > 0) { array_splice($frames, 0, $i); } $firstFrame = $this->getFrameFromException($this->exception); array_unshift($frames, $firstFrame); $this->frames = new FrameCollection($frames); if ($previousInspector = $this->getPreviousExceptionInspector()) { // Keep outer frame on top of the inner one $outerFrames = $this->frames; $newFrames = clone $previousInspector->getFrames(); // I assume it will always be set, but let's be safe if (isset($newFrames[0])) { $newFrames[0]->addComment( $previousInspector->getExceptionMessage(), 'Exception message:' ); } $newFrames->prependFrames($outerFrames->topDiff($newFrames)); $this->frames = $newFrames; } // Apply frame filters callbacks on the frames stack if (!empty($frameFilters)) { foreach ($frameFilters as $filterCallback) { $this->frames->filter($filterCallback); } } } return $this->frames; } /** * Gets the backtrace from an exception. * * If xdebug is installed * * @param \Throwable $e * @return array */ protected function getTrace($e) { $traces = $e->getTrace(); // Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default if (!$e instanceof \ErrorException) { return $traces; } if (!Misc::isLevelFatal($e->getSeverity())) { return $traces; } if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) { return $traces; } // Use xdebug to get the full stack trace and remove the shutdown handler stack trace $stack = array_reverse(xdebug_get_function_stack()); $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $traces = array_diff_key($stack, $trace); return $traces; } /** * Given an exception, generates an array in the format * generated by Exception::getTrace() * @param \Throwable $exception * @return array */ protected function getFrameFromException($exception) { return [ 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'class' => get_class($exception), 'args' => [ $exception->getMessage(), ], ]; } /** * Given an error, generates an array in the format * generated by ErrorException * @param ErrorException $exception * @return array */ protected function getFrameFromError(ErrorException $exception) { return [ 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'class' => null, 'args' => [], ]; } /** * Determine if the frame can be used to fill in previous frame's missing info * happens for call_user_func and call_user_func_array usages (PHP Bug #44428) * * @return bool */ protected function isValidNextFrame(array $frame) { if (empty($frame['file'])) { return false; } if (empty($frame['line'])) { return false; } if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) { return false; } return true; } } PKcRZ"sCcc#Whoops/Exception/ErrorException.phpnu[ */ namespace Whoops\Exception; use ErrorException as BaseErrorException; /** * Wraps ErrorException; mostly used for typing (at least now) * to easily cleanup the stack trace of redundant info. */ class ErrorException extends BaseErrorException { } PKcRZ9Un Whoops/Exception/Formatter.phpnu[ */ namespace Whoops\Exception; use Whoops\Inspector\InspectorInterface; class Formatter { /** * Returns all basic information about the exception in a simple array * for further convertion to other languages * @param InspectorInterface $inspector * @param bool $shouldAddTrace * @param array $frameFilters * @return array */ public static function formatExceptionAsDataArray(InspectorInterface $inspector, $shouldAddTrace, array $frameFilters = []) { $exception = $inspector->getException(); $response = [ 'type' => get_class($exception), 'message' => $exception->getMessage(), 'code' => $exception->getCode(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), ]; if ($shouldAddTrace) { $frames = $inspector->getFrames($frameFilters); $frameData = []; foreach ($frames as $frame) { /** @var Frame $frame */ $frameData[] = [ 'file' => $frame->getFile(), 'line' => $frame->getLine(), 'function' => $frame->getFunction(), 'class' => $frame->getClass(), 'args' => $frame->getArgs(), ]; } $response['trace'] = $frameData; } return $response; } public static function formatExceptionPlain(InspectorInterface $inspector) { $message = $inspector->getException()->getMessage(); $frames = $inspector->getFrames(); $plain = $inspector->getExceptionName(); $plain .= ' thrown with message "'; $plain .= $message; $plain .= '"'."\n\n"; $plain .= "Stacktrace:\n"; foreach ($frames as $i => $frame) { $plain .= "#". (count($frames) - $i - 1). " "; $plain .= $frame->getClass() ?: ''; $plain .= $frame->getClass() && $frame->getFunction() ? ":" : ""; $plain .= $frame->getFunction() ?: ''; $plain .= ' in '; $plain .= ($frame->getFile() ?: '<#unknown>'); $plain .= ':'; $plain .= (int) $frame->getLine(). "\n"; } return $plain; } } PKcRZŢWhoops/Exception/Frame.phpnu[ */ namespace Whoops\Exception; use InvalidArgumentException; use Serializable; class Frame implements Serializable { /** * @var array */ protected $frame; /** * @var string */ protected $fileContentsCache; /** * @var array[] */ protected $comments = []; /** * @var bool */ protected $application; public function __construct(array $frame) { $this->frame = $frame; } /** * @param bool $shortened * @return string|null */ public function getFile($shortened = false) { if (empty($this->frame['file'])) { return null; } $file = $this->frame['file']; // Check if this frame occurred within an eval(). // @todo: This can be made more reliable by checking if we've entered // eval() in a previous trace, but will need some more work on the upper // trace collector(s). if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) { $file = $this->frame['file'] = $matches[1]; $this->frame['line'] = (int) $matches[2]; } if ($shortened && is_string($file)) { // Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks. $dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); if ($dirname !== '/') { $file = str_replace($dirname, "…", $file); } $file = str_replace("/", "/­", $file); } return $file; } /** * @return int|null */ public function getLine() { return isset($this->frame['line']) ? $this->frame['line'] : null; } /** * @return string|null */ public function getClass() { return isset($this->frame['class']) ? $this->frame['class'] : null; } /** * @return string|null */ public function getFunction() { return isset($this->frame['function']) ? $this->frame['function'] : null; } /** * @return array */ public function getArgs() { return isset($this->frame['args']) ? (array) $this->frame['args'] : []; } /** * Returns the full contents of the file for this frame, * if it's known. * @return string|null */ public function getFileContents() { if ($this->fileContentsCache === null && $filePath = $this->getFile()) { // Leave the stage early when 'Unknown' or '[internal]' is passed // this would otherwise raise an exception when // open_basedir is enabled. if ($filePath === "Unknown" || $filePath === '[internal]') { return null; } try { $this->fileContentsCache = file_get_contents($filePath); } catch (ErrorException $exception) { // Internal file paths of PHP extensions cannot be opened } } return $this->fileContentsCache; } /** * Adds a comment to this frame, that can be received and * used by other handlers. For example, the PrettyPage handler * can attach these comments under the code for each frame. * * An interesting use for this would be, for example, code analysis * & annotations. * * @param string $comment * @param string $context Optional string identifying the origin of the comment */ public function addComment($comment, $context = 'global') { $this->comments[] = [ 'comment' => $comment, 'context' => $context, ]; } /** * Returns all comments for this frame. Optionally allows * a filter to only retrieve comments from a specific * context. * * @param string $filter * @return array[] */ public function getComments($filter = null) { $comments = $this->comments; if ($filter !== null) { $comments = array_filter($comments, function ($c) use ($filter) { return $c['context'] == $filter; }); } return $comments; } /** * Returns the array containing the raw frame data from which * this Frame object was built * * @return array */ public function getRawFrame() { return $this->frame; } /** * Returns the contents of the file for this frame as an * array of lines, and optionally as a clamped range of lines. * * NOTE: lines are 0-indexed * * @example * Get all lines for this file * $frame->getFileLines(); // => array( 0 => ' '...', ...) * @example * Get one line for this file, starting at line 10 (zero-indexed, remember!) * $frame->getFileLines(9, 1); // array( 9 => '...' ) * * @throws InvalidArgumentException if $length is less than or equal to 0 * @param int $start * @param int $length * @return string[]|null */ public function getFileLines($start = 0, $length = null) { if (null !== ($contents = $this->getFileContents())) { $lines = explode("\n", $contents); // Get a subset of lines from $start to $end if ($length !== null) { $start = (int) $start; $length = (int) $length; if ($start < 0) { $start = 0; } if ($length <= 0) { throw new InvalidArgumentException( "\$length($length) cannot be lower or equal to 0" ); } $lines = array_slice($lines, $start, $length, true); } return $lines; } } /** * Implements the Serializable interface, with special * steps to also save the existing comments. * * @see Serializable::serialize * @return string */ public function serialize() { $frame = $this->frame; if (!empty($this->comments)) { $frame['_comments'] = $this->comments; } return serialize($frame); } public function __serialize() { $frame = $this->frame; if (!empty($this->comments)) { $frame['_comments'] = $this->comments; } return $frame; } /** * Unserializes the frame data, while also preserving * any existing comment data. * * @see Serializable::unserialize * @param string $serializedFrame */ public function unserialize($serializedFrame) { $frame = unserialize($serializedFrame); if (!empty($frame['_comments'])) { $this->comments = $frame['_comments']; unset($frame['_comments']); } $this->frame = $frame; } public function __unserialize($frame) { if (!empty($frame['_comments'])) { $this->comments = $frame['_comments']; unset($frame['_comments']); } $this->frame = $frame; } /** * Compares Frame against one another * @param Frame $frame * @return bool */ public function equals(Frame $frame) { if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) { return false; } return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine(); } /** * Returns whether this frame belongs to the application or not. * * @return boolean */ public function isApplication() { return $this->application; } /** * Mark as an frame belonging to the application. * * @param boolean $application */ public function setApplication($application) { $this->application = $application; } } PKcRZkZ==&Whoops/Handler/JsonResponseHandler.phpnu[ */ namespace Whoops\Handler; use Whoops\Exception\Formatter; /** * Catches an exception and converts it to a JSON * response. Additionally can also return exception * frames for consumption by an API. */ class JsonResponseHandler extends Handler { /** * @var bool */ private $returnFrames = false; /** * @var bool */ private $jsonApi = false; /** * Returns errors[[]] instead of error[] to be in compliance with the json:api spec * @param bool $jsonApi Default is false * @return static */ public function setJsonApi($jsonApi = false) { $this->jsonApi = (bool) $jsonApi; return $this; } /** * @param bool|null $returnFrames * @return bool|static */ public function addTraceToOutput($returnFrames = null) { if (func_num_args() == 0) { return $this->returnFrames; } $this->returnFrames = (bool) $returnFrames; return $this; } /** * @return int */ public function handle() { if ($this->jsonApi === true) { $response = [ 'errors' => [ Formatter::formatExceptionAsDataArray( $this->getInspector(), $this->addTraceToOutput(), $this->getRun()->getFrameFilters() ), ] ]; } else { $response = [ 'error' => Formatter::formatExceptionAsDataArray( $this->getInspector(), $this->addTraceToOutput(), $this->getRun()->getFrameFilters() ), ]; } echo json_encode($response, defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0); return Handler::QUIT; } /** * @return string */ public function contentType() { return 'application/json'; } } PKcRZBU$HH"Whoops/Handler/CallbackHandler.phpnu[ */ namespace Whoops\Handler; use InvalidArgumentException; /** * Wrapper for Closures passed as handlers. Can be used * directly, or will be instantiated automagically by Whoops\Run * if passed to Run::pushHandler */ class CallbackHandler extends Handler { /** * @var callable */ protected $callable; /** * @throws InvalidArgumentException If argument is not callable * @param callable $callable */ public function __construct($callable) { if (!is_callable($callable)) { throw new InvalidArgumentException( 'Argument to ' . __METHOD__ . ' must be valid callable' ); } $this->callable = $callable; } /** * @return int|null */ public function handle() { $exception = $this->getException(); $inspector = $this->getInspector(); $run = $this->getRun(); $callable = $this->callable; // invoke the callable directly, to get simpler stacktraces (in comparison to call_user_func). // this assumes that $callable is a properly typed php-callable, which we check in __construct(). return $callable($exception, $inspector, $run); } } PKcRZU # ##Whoops/Handler/PlainTextHandler.phpnu[ * Plaintext handler for command line and logs. * @author Pierre-Yves Landuré */ namespace Whoops\Handler; use InvalidArgumentException; use Psr\Log\LoggerInterface; use Whoops\Exception\Frame; /** * Handler outputing plaintext error messages. Can be used * directly, or will be instantiated automagically by Whoops\Run * if passed to Run::pushHandler */ class PlainTextHandler extends Handler { const VAR_DUMP_PREFIX = ' | '; /** * @var \Psr\Log\LoggerInterface */ protected $logger; /** * @var callable */ protected $dumper; /** * @var bool */ private $addTraceToOutput = true; /** * @var bool|integer */ private $addTraceFunctionArgsToOutput = false; /** * @var integer */ private $traceFunctionArgsOutputLimit = 1024; /** * @var bool */ private $addPreviousToOutput = true; /** * @var bool */ private $loggerOnly = false; /** * Constructor. * @throws InvalidArgumentException If argument is not null or a LoggerInterface * @param \Psr\Log\LoggerInterface|null $logger */ public function __construct($logger = null) { $this->setLogger($logger); } /** * Set the output logger interface. * @throws InvalidArgumentException If argument is not null or a LoggerInterface * @param \Psr\Log\LoggerInterface|null $logger */ public function setLogger($logger = null) { if (! (is_null($logger) || $logger instanceof LoggerInterface)) { throw new InvalidArgumentException( 'Argument to ' . __METHOD__ . " must be a valid Logger Interface (aka. Monolog), " . get_class($logger) . ' given.' ); } $this->logger = $logger; } /** * @return \Psr\Log\LoggerInterface|null */ public function getLogger() { return $this->logger; } /** * Set var dumper callback function. * * @param callable $dumper * @return static */ public function setDumper(callable $dumper) { $this->dumper = $dumper; return $this; } /** * Add error trace to output. * @param bool|null $addTraceToOutput * @return bool|static */ public function addTraceToOutput($addTraceToOutput = null) { if (func_num_args() == 0) { return $this->addTraceToOutput; } $this->addTraceToOutput = (bool) $addTraceToOutput; return $this; } /** * Add previous exceptions to output. * @param bool|null $addPreviousToOutput * @return bool|static */ public function addPreviousToOutput($addPreviousToOutput = null) { if (func_num_args() == 0) { return $this->addPreviousToOutput; } $this->addPreviousToOutput = (bool) $addPreviousToOutput; return $this; } /** * Add error trace function arguments to output. * Set to True for all frame args, or integer for the n first frame args. * @param bool|integer|null $addTraceFunctionArgsToOutput * @return static|bool|integer */ public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null) { if (func_num_args() == 0) { return $this->addTraceFunctionArgsToOutput; } if (! is_integer($addTraceFunctionArgsToOutput)) { $this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput; } else { $this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput; } return $this; } /** * Set the size limit in bytes of frame arguments var_dump output. * If the limit is reached, the var_dump output is discarded. * Prevent memory limit errors. * @var integer * @return static */ public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit) { $this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit; return $this; } /** * Create plain text response and return it as a string * @return string */ public function generateResponse() { $exception = $this->getException(); $message = $this->getExceptionOutput($exception); if ($this->addPreviousToOutput) { $previous = $exception->getPrevious(); while ($previous) { $message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous); $previous = $previous->getPrevious(); } } return $message . $this->getTraceOutput() . "\n"; } /** * Get the size limit in bytes of frame arguments var_dump output. * If the limit is reached, the var_dump output is discarded. * Prevent memory limit errors. * @return integer */ public function getTraceFunctionArgsOutputLimit() { return $this->traceFunctionArgsOutputLimit; } /** * Only output to logger. * @param bool|null $loggerOnly * @return static|bool */ public function loggerOnly($loggerOnly = null) { if (func_num_args() == 0) { return $this->loggerOnly; } $this->loggerOnly = (bool) $loggerOnly; return $this; } /** * Test if handler can output to stdout. * @return bool */ private function canOutput() { return !$this->loggerOnly(); } /** * Get the frame args var_dump. * @param \Whoops\Exception\Frame $frame [description] * @param integer $line [description] * @return string */ private function getFrameArgsOutput(Frame $frame, $line) { if ($this->addTraceFunctionArgsToOutput() === false || $this->addTraceFunctionArgsToOutput() < $line) { return ''; } // Dump the arguments: ob_start(); $this->dump($frame->getArgs()); if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) { // The argument var_dump is to big. // Discarded to limit memory usage. ob_clean(); return sprintf( "\n%sArguments dump length greater than %d Bytes. Discarded.", self::VAR_DUMP_PREFIX, $this->getTraceFunctionArgsOutputLimit() ); } return sprintf( "\n%s", preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean()) ); } /** * Dump variable. * * @param mixed $var * @return void */ protected function dump($var) { if ($this->dumper) { call_user_func($this->dumper, $var); } else { var_dump($var); } } /** * Get the exception trace as plain text. * @return string */ private function getTraceOutput() { if (! $this->addTraceToOutput()) { return ''; } $inspector = $this->getInspector(); $frames = $inspector->getFrames($this->getRun()->getFrameFilters()); $response = "\nStack trace:"; $line = 1; foreach ($frames as $frame) { /** @var Frame $frame */ $class = $frame->getClass(); $template = "\n%3d. %s->%s() %s:%d%s"; if (! $class) { // Remove method arrow (->) from output. $template = "\n%3d. %s%s() %s:%d%s"; } $response .= sprintf( $template, $line, $class, $frame->getFunction(), $frame->getFile(), $frame->getLine(), $this->getFrameArgsOutput($frame, $line) ); $line++; } return $response; } /** * Get the exception as plain text. * @param \Throwable $exception * @return string */ private function getExceptionOutput($exception) { return sprintf( "%s: %s in file %s on line %d", get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine() ); } /** * @return int */ public function handle() { $response = $this->generateResponse(); if ($this->getLogger()) { $this->getLogger()->error($response); } if (! $this->canOutput()) { return Handler::DONE; } echo $response; return Handler::QUIT; } /** * @return string */ public function contentType() { return 'text/plain'; } } PKcRZԷ#Whoops/Handler/HandlerInterface.phpnu[ */ namespace Whoops\Handler; use Whoops\Inspector\InspectorInterface; use Whoops\RunInterface; interface HandlerInterface { /** * @return int|null A handler may return nothing, or a Handler::HANDLE_* constant */ public function handle(); /** * @param RunInterface $run * @return void */ public function setRun(RunInterface $run); /** * @param \Throwable $exception * @return void */ public function setException($exception); /** * @param InspectorInterface $inspector * @return void */ public function setInspector(InspectorInterface $inspector); } PKcRZzzWhoops/Handler/Handler.phpnu[ */ namespace Whoops\Handler; use Whoops\Inspector\InspectorInterface; use Whoops\RunInterface; /** * Abstract implementation of a Handler. */ abstract class Handler implements HandlerInterface { /* Return constants that can be returned from Handler::handle to message the handler walker. */ const DONE = 0x10; // returning this is optional, only exists for // semantic purposes /** * The Handler has handled the Throwable in some way, and wishes to skip any other Handler. * Execution will continue. */ const LAST_HANDLER = 0x20; /** * The Handler has handled the Throwable in some way, and wishes to quit/stop execution */ const QUIT = 0x30; /** * @var RunInterface */ private $run; /** * @var InspectorInterface $inspector */ private $inspector; /** * @var \Throwable $exception */ private $exception; /** * @param RunInterface $run */ public function setRun(RunInterface $run) { $this->run = $run; } /** * @return RunInterface */ protected function getRun() { return $this->run; } /** * @param InspectorInterface $inspector */ public function setInspector(InspectorInterface $inspector) { $this->inspector = $inspector; } /** * @return InspectorInterface */ protected function getInspector() { return $this->inspector; } /** * @param \Throwable $exception */ public function setException($exception) { $this->exception = $exception; } /** * @return \Throwable */ protected function getException() { return $this->exception; } } PKcRZ A %Whoops/Handler/XmlResponseHandler.phpnu[ */ namespace Whoops\Handler; use SimpleXMLElement; use Whoops\Exception\Formatter; /** * Catches an exception and converts it to an XML * response. Additionally can also return exception * frames for consumption by an API. */ class XmlResponseHandler extends Handler { /** * @var bool */ private $returnFrames = false; /** * @param bool|null $returnFrames * @return bool|static */ public function addTraceToOutput($returnFrames = null) { if (func_num_args() == 0) { return $this->returnFrames; } $this->returnFrames = (bool) $returnFrames; return $this; } /** * @return int */ public function handle() { $response = [ 'error' => Formatter::formatExceptionAsDataArray( $this->getInspector(), $this->addTraceToOutput(), $this->getRun()->getFrameFilters() ), ]; echo self::toXml($response); return Handler::QUIT; } /** * @return string */ public function contentType() { return 'application/xml'; } /** * @param SimpleXMLElement $node Node to append data to, will be modified in place * @param array|\Traversable $data * @return SimpleXMLElement The modified node, for chaining */ private static function addDataToNode(\SimpleXMLElement $node, $data) { assert(is_array($data) || $data instanceof Traversable); foreach ($data as $key => $value) { if (is_numeric($key)) { // Convert the key to a valid string $key = "unknownNode_". (string) $key; } // Delete any char not allowed in XML element names $key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key); if (is_array($value)) { $child = $node->addChild($key); self::addDataToNode($child, $value); } else { $value = str_replace('&', '&', print_r($value, true)); $node->addChild($key, $value); } } return $node; } /** * The main function for converting to an XML document. * * @param array|\Traversable $data * @return string XML */ private static function toXml($data) { assert(is_array($data) || $data instanceof Traversable); $node = simplexml_load_string(""); return self::addDataToNode($node, $data)->asXML(); } } PKcRZ*ܤdd$Whoops/Handler/PrettyPageHandler.phpnu[ */ namespace Whoops\Handler; use InvalidArgumentException; use RuntimeException; use Symfony\Component\VarDumper\Cloner\AbstractCloner; use Symfony\Component\VarDumper\Cloner\VarCloner; use UnexpectedValueException; use Whoops\Exception\Formatter; use Whoops\Util\Misc; use Whoops\Util\TemplateHelper; class PrettyPageHandler extends Handler { const EDITOR_SUBLIME = "sublime"; const EDITOR_TEXTMATE = "textmate"; const EDITOR_EMACS = "emacs"; const EDITOR_MACVIM = "macvim"; const EDITOR_PHPSTORM = "phpstorm"; const EDITOR_IDEA = "idea"; const EDITOR_VSCODE = "vscode"; const EDITOR_ATOM = "atom"; const EDITOR_ESPRESSO = "espresso"; const EDITOR_XDEBUG = "xdebug"; const EDITOR_NETBEANS = "netbeans"; /** * Search paths to be scanned for resources. * * Stored in the reverse order they're declared. * * @var array */ private $searchPaths = []; /** * Fast lookup cache for known resource locations. * * @var array */ private $resourceCache = []; /** * The name of the custom css file. * * @var string|null */ private $customCss = null; /** * The name of the custom js file. * * @var string|null */ private $customJs = null; /** * @var array[] */ private $extraTables = []; /** * @var bool */ private $handleUnconditionally = false; /** * @var string */ private $pageTitle = "Whoops! There was an error."; /** * @var array[] */ private $applicationPaths; /** * @var array[] */ private $blacklist = [ '_GET' => [], '_POST' => [], '_FILES' => [], '_COOKIE' => [], '_SESSION' => [], '_SERVER' => [], '_ENV' => [], ]; /** * An identifier for a known IDE/text editor. * * Either a string, or a calalble that resolves a string, that can be used * to open a given file in an editor. If the string contains the special * substrings %file or %line, they will be replaced with the correct data. * * @example * "txmt://open?url=%file&line=%line" * * @var callable|string $editor */ protected $editor; /** * A list of known editor strings. * * @var array */ protected $editors = [ "sublime" => "subl://open?url=file://%file&line=%line", "textmate" => "txmt://open?url=file://%file&line=%line", "emacs" => "emacs://open?url=file://%file&line=%line", "macvim" => "mvim://open/?url=file://%file&line=%line", "phpstorm" => "phpstorm://open?file=%file&line=%line", "idea" => "idea://open?file=%file&line=%line", "vscode" => "vscode://file/%file:%line", "atom" => "atom://core/open/file?filename=%file&line=%line", "espresso" => "x-espresso://open?filepath=%file&lines=%line", "netbeans" => "netbeans://open/?f=%file:%line", ]; /** * @var TemplateHelper */ protected $templateHelper; /** * Constructor. * * @return void */ public function __construct() { if (ini_get('xdebug.file_link_format') || get_cfg_var('xdebug.file_link_format')) { // Register editor using xdebug's file_link_format option. $this->editors['xdebug'] = function ($file, $line) { return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')); }; // If xdebug is available, use it as default editor. $this->setEditor('xdebug'); } // Add the default, local resource search path: $this->searchPaths[] = __DIR__ . "/../Resources"; // blacklist php provided auth based values $this->blacklist('_SERVER', 'PHP_AUTH_PW'); $this->templateHelper = new TemplateHelper(); if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) { $cloner = new VarCloner(); // Only dump object internals if a custom caster exists for performance reasons // https://github.com/filp/whoops/pull/404 $cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) { $class = $stub->class; $classes = [$class => $class] + class_parents($obj) + class_implements($obj); foreach ($classes as $class) { if (isset(AbstractCloner::$defaultCasters[$class])) { return $a; } } // Remove all internals return []; }]); $this->templateHelper->setCloner($cloner); } } /** * @return int|null * * @throws \Exception */ public function handle() { if (!$this->handleUnconditionally()) { // Check conditions for outputting HTML: // @todo: Make this more robust if (PHP_SAPI === 'cli') { // Help users who have been relying on an internal test value // fix their code to the proper method if (isset($_ENV['whoops-test'])) { throw new \Exception( 'Use handleUnconditionally instead of whoops-test' .' environment variable' ); } return Handler::DONE; } } $templateFile = $this->getResource("views/layout.html.php"); $cssFile = $this->getResource("css/whoops.base.css"); $zeptoFile = $this->getResource("js/zepto.min.js"); $prismJs = $this->getResource("js/prism.js"); $prismCss = $this->getResource("css/prism.css"); $clipboard = $this->getResource("js/clipboard.min.js"); $jsFile = $this->getResource("js/whoops.base.js"); if ($this->customCss) { $customCssFile = $this->getResource($this->customCss); } if ($this->customJs) { $customJsFile = $this->getResource($this->customJs); } $inspector = $this->getInspector(); $frames = $this->getExceptionFrames(); $code = $this->getExceptionCode(); // List of variables that will be passed to the layout template. $vars = [ "page_title" => $this->getPageTitle(), // @todo: Asset compiler "stylesheet" => file_get_contents($cssFile), "zepto" => file_get_contents($zeptoFile), "prismJs" => file_get_contents($prismJs), "prismCss" => file_get_contents($prismCss), "clipboard" => file_get_contents($clipboard), "javascript" => file_get_contents($jsFile), // Template paths: "header" => $this->getResource("views/header.html.php"), "header_outer" => $this->getResource("views/header_outer.html.php"), "frame_list" => $this->getResource("views/frame_list.html.php"), "frames_description" => $this->getResource("views/frames_description.html.php"), "frames_container" => $this->getResource("views/frames_container.html.php"), "panel_details" => $this->getResource("views/panel_details.html.php"), "panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"), "panel_left" => $this->getResource("views/panel_left.html.php"), "panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"), "frame_code" => $this->getResource("views/frame_code.html.php"), "env_details" => $this->getResource("views/env_details.html.php"), "title" => $this->getPageTitle(), "name" => explode("\\", $inspector->getExceptionName()), "message" => $inspector->getExceptionMessage(), "previousMessages" => $inspector->getPreviousExceptionMessages(), "docref_url" => $inspector->getExceptionDocrefUrl(), "code" => $code, "previousCodes" => $inspector->getPreviousExceptionCodes(), "plain_exception" => Formatter::formatExceptionPlain($inspector), "frames" => $frames, "has_frames" => !!count($frames), "handler" => $this, "handlers" => $this->getRun()->getHandlers(), "active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all', "has_frames_tabs" => $this->getApplicationPaths(), "tables" => [ "GET Data" => $this->masked($_GET, '_GET'), "POST Data" => $this->masked($_POST, '_POST'), "Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [], "Cookies" => $this->masked($_COOKIE, '_COOKIE'), "Session" => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [], "Server/Request Data" => $this->masked($_SERVER, '_SERVER'), "Environment Variables" => $this->masked($_ENV, '_ENV'), ], ]; if (isset($customCssFile)) { $vars["stylesheet"] .= file_get_contents($customCssFile); } if (isset($customJsFile)) { $vars["javascript"] .= file_get_contents($customJsFile); } // Add extra entries list of data tables: // @todo: Consolidate addDataTable and addDataTableCallback $extraTables = array_map(function ($table) use ($inspector) { return $table instanceof \Closure ? $table($inspector) : $table; }, $this->getDataTables()); $vars["tables"] = array_merge($extraTables, $vars["tables"]); $plainTextHandler = new PlainTextHandler(); $plainTextHandler->setRun($this->getRun()); $plainTextHandler->setException($this->getException()); $plainTextHandler->setInspector($this->getInspector()); $vars["preface"] = ""; $this->templateHelper->setVariables($vars); $this->templateHelper->render($templateFile); return Handler::QUIT; } /** * Get the stack trace frames of the exception currently being handled. * * @return \Whoops\Exception\FrameCollection */ protected function getExceptionFrames() { $frames = $this->getInspector()->getFrames($this->getRun()->getFrameFilters()); if ($this->getApplicationPaths()) { foreach ($frames as $frame) { foreach ($this->getApplicationPaths() as $path) { if (strpos($frame->getFile(), $path) === 0) { $frame->setApplication(true); break; } } } } return $frames; } /** * Get the code of the exception currently being handled. * * @return string */ protected function getExceptionCode() { $exception = $this->getException(); $code = $exception->getCode(); if ($exception instanceof \ErrorException) { // ErrorExceptions wrap the php-error types within the 'severity' property $code = Misc::translateErrorCode($exception->getSeverity()); } return (string) $code; } /** * @return string */ public function contentType() { return 'text/html'; } /** * Adds an entry to the list of tables displayed in the template. * * The expected data is a simple associative array. Any nested arrays * will be flattened with `print_r`. * * @param string $label * * @return static */ public function addDataTable($label, array $data) { $this->extraTables[$label] = $data; return $this; } /** * Lazily adds an entry to the list of tables displayed in the table. * * The supplied callback argument will be called when the error is * rendered, it should produce a simple associative array. Any nested * arrays will be flattened with `print_r`. * * @param string $label * @param callable $callback Callable returning an associative array * * @throws InvalidArgumentException If $callback is not callable * * @return static */ public function addDataTableCallback($label, /* callable */ $callback) { if (!is_callable($callback)) { throw new InvalidArgumentException('Expecting callback argument to be callable'); } $this->extraTables[$label] = function (\Whoops\Inspector\InspectorInterface $inspector = null) use ($callback) { try { $result = call_user_func($callback, $inspector); // Only return the result if it can be iterated over by foreach(). return is_array($result) || $result instanceof \Traversable ? $result : []; } catch (\Exception $e) { // Don't allow failure to break the rendering of the original exception. return []; } }; return $this; } /** * Returns all the extra data tables registered with this handler. * * Optionally accepts a 'label' parameter, to only return the data table * under that label. * * @param string|null $label * * @return array[]|callable */ public function getDataTables($label = null) { if ($label !== null) { return isset($this->extraTables[$label]) ? $this->extraTables[$label] : []; } return $this->extraTables; } /** * Set whether to handle unconditionally. * * Allows to disable all attempts to dynamically decide whether to handle * or return prematurely. Set this to ensure that the handler will perform, * no matter what. * * @param bool|null $value * * @return bool|static */ public function handleUnconditionally($value = null) { if (func_num_args() == 0) { return $this->handleUnconditionally; } $this->handleUnconditionally = (bool) $value; return $this; } /** * Adds an editor resolver. * * Either a string, or a closure that resolves a string, that can be used * to open a given file in an editor. If the string contains the special * substrings %file or %line, they will be replaced with the correct data. * * @example * $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line") * @example * $run->addEditor('remove-it', function($file, $line) { * unlink($file); * return "http://stackoverflow.com"; * }); * * @param string $identifier * @param string|callable $resolver * * @return static */ public function addEditor($identifier, $resolver) { $this->editors[$identifier] = $resolver; return $this; } /** * Set the editor to use to open referenced files. * * Pass either the name of a configured editor, or a closure that directly * resolves an editor string. * * @example * $run->setEditor(function($file, $line) { return "file:///{$file}"; }); * @example * $run->setEditor('sublime'); * * @param string|callable $editor * * @throws InvalidArgumentException If invalid argument identifier provided * * @return static */ public function setEditor($editor) { if (!is_callable($editor) && !isset($this->editors[$editor])) { throw new InvalidArgumentException( "Unknown editor identifier: $editor. Known editors:" . implode(",", array_keys($this->editors)) ); } $this->editor = $editor; return $this; } /** * Get the editor href for a given file and line, if available. * * @param string $filePath * @param int $line * * @throws InvalidArgumentException If editor resolver does not return a string * * @return string|bool */ public function getEditorHref($filePath, $line) { $editor = $this->getEditor($filePath, $line); if (empty($editor)) { return false; } // Check that the editor is a string, and replace the // %line and %file placeholders: if (!isset($editor['url']) || !is_string($editor['url'])) { throw new UnexpectedValueException( __METHOD__ . " should always resolve to a string or a valid editor array; got something else instead." ); } $editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']); $editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']); return $editor['url']; } /** * Determine if the editor link should act as an Ajax request. * * @param string $filePath * @param int $line * * @throws UnexpectedValueException If editor resolver does not return a boolean * * @return bool */ public function getEditorAjax($filePath, $line) { $editor = $this->getEditor($filePath, $line); // Check that the ajax is a bool if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) { throw new UnexpectedValueException( __METHOD__ . " should always resolve to a bool; got something else instead." ); } return $editor['ajax']; } /** * Determines both the editor and if ajax should be used. * * @param string $filePath * @param int $line * * @return array */ protected function getEditor($filePath, $line) { if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) { return []; } if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) { return [ 'ajax' => false, 'url' => $this->editors[$this->editor], ]; } if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) { if (is_callable($this->editor)) { $callback = call_user_func($this->editor, $filePath, $line); } else { $callback = call_user_func($this->editors[$this->editor], $filePath, $line); } if (empty($callback)) { return []; } if (is_string($callback)) { return [ 'ajax' => false, 'url' => $callback, ]; } return [ 'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false, 'url' => isset($callback['url']) ? $callback['url'] : $callback, ]; } return []; } /** * Set the page title. * * @param string $title * * @return static */ public function setPageTitle($title) { $this->pageTitle = (string) $title; return $this; } /** * Get the page title. * * @return string */ public function getPageTitle() { return $this->pageTitle; } /** * Adds a path to the list of paths to be searched for resources. * * @param string $path * * @throws InvalidArgumentException If $path is not a valid directory * * @return static */ public function addResourcePath($path) { if (!is_dir($path)) { throw new InvalidArgumentException( "'$path' is not a valid directory" ); } array_unshift($this->searchPaths, $path); return $this; } /** * Adds a custom css file to be loaded. * * @param string|null $name * * @return static */ public function addCustomCss($name) { $this->customCss = $name; return $this; } /** * Adds a custom js file to be loaded. * * @param string|null $name * * @return static */ public function addCustomJs($name) { $this->customJs = $name; return $this; } /** * @return array */ public function getResourcePaths() { return $this->searchPaths; } /** * Finds a resource, by its relative path, in all available search paths. * * The search is performed starting at the last search path, and all the * way back to the first, enabling a cascading-type system of overrides for * all resources. * * @param string $resource * * @throws RuntimeException If resource cannot be found in any of the available paths * * @return string */ protected function getResource($resource) { // If the resource was found before, we can speed things up // by caching its absolute, resolved path: if (isset($this->resourceCache[$resource])) { return $this->resourceCache[$resource]; } // Search through available search paths, until we find the // resource we're after: foreach ($this->searchPaths as $path) { $fullPath = $path . "/$resource"; if (is_file($fullPath)) { // Cache the result: $this->resourceCache[$resource] = $fullPath; return $fullPath; } } // If we got this far, nothing was found. throw new RuntimeException( "Could not find resource '$resource' in any resource paths." . "(searched: " . join(", ", $this->searchPaths). ")" ); } /** * @deprecated * * @return string */ public function getResourcesPath() { $allPaths = $this->getResourcePaths(); // Compat: return only the first path added return end($allPaths) ?: null; } /** * @deprecated * * @param string $resourcesPath * * @return static */ public function setResourcesPath($resourcesPath) { $this->addResourcePath($resourcesPath); return $this; } /** * Return the application paths. * * @return array */ public function getApplicationPaths() { return $this->applicationPaths; } /** * Set the application paths. * * @return void */ public function setApplicationPaths(array $applicationPaths) { $this->applicationPaths = $applicationPaths; } /** * Set the application root path. * * @param string $applicationRootPath * * @return void */ public function setApplicationRootPath($applicationRootPath) { $this->templateHelper->setApplicationRootPath($applicationRootPath); } /** * blacklist a sensitive value within one of the superglobal arrays. * Alias for the hideSuperglobalKey method. * * @param string $superGlobalName The name of the superglobal array, e.g. '_GET' * @param string $key The key within the superglobal * @see hideSuperglobalKey * * @return static */ public function blacklist($superGlobalName, $key) { $this->blacklist[$superGlobalName][] = $key; return $this; } /** * Hide a sensitive value within one of the superglobal arrays. * * @param string $superGlobalName The name of the superglobal array, e.g. '_GET' * @param string $key The key within the superglobal * @return static */ public function hideSuperglobalKey($superGlobalName, $key) { return $this->blacklist($superGlobalName, $key); } /** * Checks all values within the given superGlobal array. * * Blacklisted values will be replaced by a equal length string containing * only '*' characters for string values. * Non-string values will be replaced with a fixed asterisk count. * We intentionally dont rely on $GLOBALS as it depends on the 'auto_globals_jit' php.ini setting. * * @param array|\ArrayAccess $superGlobal One of the superglobal arrays * @param string $superGlobalName The name of the superglobal array, e.g. '_GET' * * @return array $values without sensitive data */ private function masked($superGlobal, $superGlobalName) { $blacklisted = $this->blacklist[$superGlobalName]; $values = $superGlobal; foreach ($blacklisted as $key) { if (isset($superGlobal[$key])) { $values[$key] = str_repeat('*', is_string($superGlobal[$key]) ? strlen($superGlobal[$key]) : 3); } } return $values; } } PKcRZىWhoops/RunInterface.phpnu[ */ namespace Whoops; use InvalidArgumentException; use Whoops\Exception\ErrorException; use Whoops\Handler\HandlerInterface; interface RunInterface { const EXCEPTION_HANDLER = "handleException"; const ERROR_HANDLER = "handleError"; const SHUTDOWN_HANDLER = "handleShutdown"; /** * Pushes a handler to the end of the stack * * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface * @param Callable|HandlerInterface $handler * @return Run */ public function pushHandler($handler); /** * Removes the last handler in the stack and returns it. * Returns null if there"s nothing else to pop. * * @return null|HandlerInterface */ public function popHandler(); /** * Returns an array with all handlers, in the * order they were added to the stack. * * @return array */ public function getHandlers(); /** * Clears all handlers in the handlerStack, including * the default PrettyPage handler. * * @return Run */ public function clearHandlers(); /** * @return array */ public function getFrameFilters(); /** * @return Run */ public function clearFrameFilters(); /** * Registers this instance as an error handler. * * @return Run */ public function register(); /** * Unregisters all handlers registered by this Whoops\Run instance * * @return Run */ public function unregister(); /** * Should Whoops allow Handlers to force the script to quit? * * @param bool|int $exit * @return bool */ public function allowQuit($exit = null); /** * Silence particular errors in particular files * * @param array|string $patterns List or a single regex pattern to match * @param int $levels Defaults to E_STRICT | E_DEPRECATED * @return \Whoops\Run */ public function silenceErrorsInPaths($patterns, $levels = 10240); /** * Should Whoops send HTTP error code to the browser if possible? * Whoops will by default send HTTP code 500, but you may wish to * use 502, 503, or another 5xx family code. * * @param bool|int $code * @return int|false */ public function sendHttpCode($code = null); /** * Should Whoops exit with a specific code on the CLI if possible? * Whoops will exit with 1 by default, but you can specify something else. * * @param int $code * @return int */ public function sendExitCode($code = null); /** * Should Whoops push output directly to the client? * If this is false, output will be returned by handleException * * @param bool|int $send * @return bool */ public function writeToOutput($send = null); /** * Handles an exception, ultimately generating a Whoops error * page. * * @param \Throwable $exception * @return string Output generated by handlers */ public function handleException($exception); /** * Converts generic PHP errors to \ErrorException * instances, before passing them off to be handled. * * This method MUST be compatible with set_error_handler. * * @param int $level * @param string $message * @param string $file * @param int $line * * @return bool * @throws ErrorException */ public function handleError($level, $message, $file = null, $line = null); /** * Special case to deal with Fatal errors and the like. */ public function handleShutdown(); /** * Registers a filter callback in the frame filters stack. * * @param callable $filterCallback * @return \Whoops\Run */ public function addFrameFilter($filterCallback); } PKcRZ$ȁ4 Whoops/Resources/css/prism.cssnu[/* PrismJS 1.29.0 https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php&plugins=line-highlight+line-numbers */ code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)} pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right} PKcRZ5(5($Whoops/Resources/css/whoops.base.cssnu[body { font: 12px "Helvetica Neue", helvetica, arial, sans-serif; color: #131313; background: #eeeeee; padding:0; margin: 0; max-height: 100%; text-rendering: optimizeLegibility; } a { text-decoration: none; } .Whoops.container { position: relative; z-index: 9999999999; } .panel { overflow-y: scroll; height: 100%; position: fixed; margin: 0; left: 0; top: 0; } .branding { position: absolute; top: 10px; right: 20px; color: #777777; font-size: 10px; z-index: 100; } .branding a { color: #e95353; } header { color: white; box-sizing: border-box; background-color: #2a2a2a; padding: 35px 40px; max-height: 180px; overflow: hidden; transition: 0.5s; } header.header-expand { max-height: 1000px; } .exc-title { margin: 0; color: #bebebe; font-size: 14px; } .exc-title-primary, .exc-title-secondary { color: #e95353; } .exc-message { font-size: 20px; word-wrap: break-word; margin: 4px 0 0 0; color: white; } .exc-message span { display: block; } .exc-message-empty-notice { color: #a29d9d; font-weight: 300; } .prev-exc-title { margin: 10px 0; } .prev-exc-title + ul { margin: 0; padding: 0 0 0 20px; line-height: 12px; } .prev-exc-title + ul li { font: 12px "Helvetica Neue", helvetica, arial, sans-serif; } .prev-exc-title + ul li .prev-exc-code { display: inline-block; color: #bebebe; } .details-container { left: 30%; width: 70%; background: #fafafa; } .details { padding: 5px; } .details-heading { color: #4288CE; font-weight: 300; padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid rgba(0, 0, 0, .1); } .details pre.sf-dump { white-space: pre; word-wrap: inherit; } .details pre.sf-dump, .details pre.sf-dump .sf-dump-num, .details pre.sf-dump .sf-dump-const, .details pre.sf-dump .sf-dump-str, .details pre.sf-dump .sf-dump-note, .details pre.sf-dump .sf-dump-ref, .details pre.sf-dump .sf-dump-public, .details pre.sf-dump .sf-dump-protected, .details pre.sf-dump .sf-dump-private, .details pre.sf-dump .sf-dump-meta, .details pre.sf-dump .sf-dump-key, .details pre.sf-dump .sf-dump-index { color: #463C54; } .left-panel { width: 30%; background: #ded8d8; } .frames-description { background: rgba(0, 0, 0, .05); padding: 8px 15px; color: #a29d9d; font-size: 11px; } .frames-description.frames-description-application { text-align: center; font-size: 12px; } .frames-container.frames-container-application .frame:not(.frame-application) { display: none; } .frames-tab { color: #a29d9d; display: inline-block; padding: 4px 8px; margin: 0 2px; border-radius: 3px; } .frames-tab.frames-tab-active { background-color: #2a2a2a; color: #bebebe; } .frame { padding: 14px; cursor: pointer; transition: all 0.1s ease; background: #eeeeee; } .frame:not(:last-child) { border-bottom: 1px solid rgba(0, 0, 0, .05); } .frame.active { box-shadow: inset -5px 0 0 0 #4288CE; color: #4288CE; } .frame:not(.active):hover { background: #BEE9EA; } .frame-method-info { margin-bottom: 10px; } .frame-class, .frame-function, .frame-index { font-size: 14px; } .frame-index { float: left; } .frame-method-info { margin-left: 24px; } .frame-index { font-size: 11px; color: #a29d9d; background-color: rgba(0, 0, 0, .05); height: 18px; width: 18px; line-height: 18px; border-radius: 5px; padding: 0 1px 0 1px; text-align: center; display: inline-block; } .frame-application .frame-index { background-color: #2a2a2a; color: #bebebe; } .frame-file { font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; color: #a29d9d; } .frame-file .editor-link { color: #a29d9d; } .frame-line { font-weight: bold; } .frame-line:before { content: ":"; } .frame-code { padding: 5px; background: #303030; display: none; } .frame-code.active { display: block; } .frame-code .frame-file { color: #a29d9d; padding: 12px 6px; border-bottom: none; } .code-block { padding: 10px; margin: 0; border-radius: 6px; box-shadow: 0 3px 0 rgba(0, 0, 0, .05), 0 10px 30px rgba(0, 0, 0, .05), inset 0 0 1px 0 rgba(255, 255, 255, .07); -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; } .linenums { margin: 0; margin-left: 10px; } .frame-comments { border-top: none; margin-top: 15px; font-size: 12px; } .frame-comments.empty { } .frame-comments.empty:before { content: "No comments for this stack frame."; font-weight: 300; color: #a29d9d; } .frame-comment { padding: 10px; color: #e3e3e3; border-radius: 6px; background-color: rgba(255, 255, 255, .05); } .frame-comment a { font-weight: bold; text-decoration: none; } .frame-comment a:hover { color: #4bb1b1; } .frame-comment:not(:last-child) { border-bottom: 1px dotted rgba(0, 0, 0, .3); } .frame-comment-context { font-size: 10px; color: white; } .delimiter { display: inline-block; } .data-table-container label { font-size: 16px; color: #303030; font-weight: bold; margin: 10px 0; display: block; margin-bottom: 5px; padding-bottom: 5px; } .data-table { width: 100%; margin-bottom: 10px; } .data-table tbody { font: 13px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; } .data-table thead { display: none; } .data-table tr { padding: 5px 0; } .data-table td:first-child { width: 20%; min-width: 130px; overflow: hidden; font-weight: bold; color: #463C54; padding-right: 5px; } .data-table td:last-child { width: 80%; -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } .data-table span.empty { color: rgba(0, 0, 0, .3); font-weight: 300; } .data-table label.empty { display: inline; } .handler { padding: 4px 0; font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace; } #plain-exception { display: none; } .rightButton { cursor: pointer; border: 0; opacity: .8; background: none; color: rgba(255, 255, 255, 0.1); box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1); border-radius: 3px; outline: none !important; } .rightButton:hover { box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3); } /* inspired by githubs kbd styles */ kbd { -moz-border-bottom-colors: none; -moz-border-left-colors: none; -moz-border-right-colors: none; -moz-border-top-colors: none; background-color: #fcfcfc; border-color: #ccc #ccc #bbb; border-image: none; border-style: solid; border-width: 1px; color: #555; display: inline-block; font-size: 11px; line-height: 10px; padding: 3px 5px; vertical-align: middle; } /* == Media queries */ /* Expand the spacing in the details section */ @media (min-width: 1000px) { .details, .frame-code { padding: 20px 40px; } .details-container { left: 32%; width: 68%; } .frames-container { margin: 5px; } .left-panel { width: 32%; } } /* Stack panels */ @media (max-width: 600px) { .panel { position: static; width: 100%; } } /* Stack details tables */ @media (max-width: 400px) { .data-table, .data-table tbody, .data-table tbody tr, .data-table tbody td { display: block; width: 100%; } .data-table tbody tr:first-child { padding-top: 0; } .data-table tbody td:first-child, .data-table tbody td:last-child { padding-left: 0; padding-right: 0; } .data-table tbody td:last-child { padding-top: 3px; } } .tooltipped { position: relative } .tooltipped:after { position: absolute; z-index: 1000000; display: none; padding: 5px 8px; color: #fff; text-align: center; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-wrap: break-word; white-space: pre; pointer-events: none; content: attr(aria-label); background: rgba(0, 0, 0, 0.8); border-radius: 3px; -webkit-font-smoothing: subpixel-antialiased } .tooltipped:before { position: absolute; z-index: 1000001; display: none; width: 0; height: 0; color: rgba(0, 0, 0, 0.8); pointer-events: none; content: ""; border: 5px solid transparent } .tooltipped:hover:before, .tooltipped:hover:after, .tooltipped:active:before, .tooltipped:active:after, .tooltipped:focus:before, .tooltipped:focus:after { display: inline-block; text-decoration: none } .tooltipped-s:after { top: 100%; right: 50%; margin-top: 5px } .tooltipped-s:before { top: auto; right: 50%; bottom: -5px; margin-right: -5px; border-bottom-color: rgba(0, 0, 0, 0.8) } pre.sf-dump { padding: 0px !important; margin: 0px !important; } .search-for-help { width: 85%; padding: 0; margin: 10px 0; list-style-type: none; display: inline-block; } .search-for-help li { display: inline-block; margin-right: 5px; } .search-for-help li:last-child { margin-right: 0; } .search-for-help li a { } .search-for-help li a i { width: 16px; height: 16px; overflow: hidden; display: block; } .search-for-help li a svg { fill: #fff; } .search-for-help li a svg path { background-size: contain; } PKcRZ2Whoops/Resources/views/frames_description.html.phpnu[
Application frames (countIsApplication() ?>) All frames () Stack frames ()
PKcRZ޲!mm&Whoops/Resources/views/layout.html.phpnu[ <?php echo $tpl->escape($page_title) ?>
render($panel_left_outer) ?> render($panel_details_outer) ?>
PKcRZ\ FF-Whoops/Resources/views/panel_details.html.phpnu[render($frame_code) ?> render($env_details) ?>PKcRZ-_+Whoops/Resources/views/env_details.html.phpnu[

Environment & details:

$data): ?>
$value): ?>
Key Value
escape($k) ?> dump($value) ?>
empty
$h): ?>
. escape(get_class($h)) ?>
PKcRZYyy0Whoops/Resources/views/panel_left_outer.html.phpnu[
render($panel_left) ?>
PKcRZGw"WW3Whoops/Resources/views/panel_details_outer.html.phpnu[
render($panel_details) ?>
PKcRZ9 9 *Whoops/Resources/views/frame_code.html.phpnu[
$frame): ?> getLine(); ?>
getFile(); ?> getEditorHref($filePath, (int) $line)): ?> getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>> Open: breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?> breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?>
getFileLines($line - 20, 40); // getFileLines can return null if there is no source code if ($range): $range = array_map(function ($line) { return empty($line) ? ' ' : $line;}, $range); $start = key($range) + 1; $code = join("\n", $range); ?>
escape($code) ?>
dumpArgs($frame); ?>
Arguments
getComments(); ?>
$comment): ?>
escape($context) ?> escapeButPreserveUris($comment) ?>
PKcRZ*Whoops/Resources/views/frame_list.html.phpnu[ $frame): ?>
breakOnDelimiter('\\', $tpl->escape($frame->getClass() ?: '')) ?> breakOnDelimiter('\\', $tpl->escape($frame->getFunction() ?: '')) ?>
getFile() ? $tpl->breakOnDelimiter('/', $tpl->shorten($tpl->escape($frame->getFile()))) : '<#unknown>' ?>getLine() ?>
render($header_outer); $tpl->render($frames_description); $tpl->render($frames_container); PKcRZ.|w44,Whoops/Resources/views/header_outer.html.phpnu[
render($header) ?>
PKcRZ W0Whoops/Resources/views/frames_container.html.phpnu[
render($frame_list) ?>
PKcRZkg%$$&Whoops/Resources/views/header.html.phpnu[
$nameSection): ?> escape($nameSection) ?> escape($nameSection) . ' \\' ?> (escape($code) ?>)
escape($message) ?>
Previous exceptions
    $previousMessage): ?>
  • escape($previousMessage) ?> ()
No message escape($plain_exception) ?>
PKcRZ>^>^Whoops/Resources/js/prism.jsnu[/* PrismJS 1.29.0 https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+markup-templating+php&plugins=line-highlight+line-numbers */ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; !function(e){function n(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(t,a,r,o){if(t.language===a){var c=t.tokenStack=[];t.code=t.code.replace(r,(function(e){if("function"==typeof o&&!o(e))return e;for(var r,i=c.length;-1!==t.code.indexOf(r=n(a,i));)++i;return c[i]=e,r})),t.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(t,a){if(t.language===a&&t.tokenStack){t.grammar=e.languages[a];var r=0,o=Object.keys(t.tokenStack);!function c(i){for(var u=0;u=o.length);u++){var g=i[u];if("string"==typeof g||g.content&&"string"==typeof g.content){var l=o[r],s=t.tokenStack[l],f="string"==typeof g?g:g.content,p=n(a,l),k=f.indexOf(p);if(k>-1){++r;var m=f.substring(0,k),d=new e.Token(a,e.tokenize(s,t.grammar),"language-"+a,s),h=f.substring(k+p.length),v=[];m&&v.push.apply(v,c([m])),v.push(d),h&&v.push.apply(v,c([h])),"string"==typeof g?i.splice.apply(i,[u,1].concat(v)):g.content=v}}else g.content&&c(g.content)}return i}(t.tokens)}}}})}(Prism); !function(e){var a=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,t=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],i=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,n=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,s=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:a,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:i,operator:n,punctuation:s};var l={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},r=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:l}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:l}}];e.languages.insertBefore("php","variable",{string:r,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:a,string:r,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:t,number:i,operator:n,punctuation:s}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(a){/<\?/.test(a.code)&&e.languages["markup-templating"].buildPlaceholders(a,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(a){e.languages["markup-templating"].tokenizePlaceholders(a,"php")}))}(Prism); !function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document&&document.querySelector){var e,t="line-numbers",i="linkable-line-numbers",n=/\n(?!$)/g,r=!0;Prism.plugins.lineHighlight={highlightLines:function(o,u,c){var h=(u="string"==typeof u?u:o.getAttribute("data-line")||"").replace(/\s+/g,"").split(",").filter(Boolean),d=+o.getAttribute("data-line-offset")||0,f=(function(){if(void 0===e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding="0",t.style.border="0",t.innerHTML=" 
 ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}()?parseInt:parseFloat)(getComputedStyle(o).lineHeight),p=Prism.util.isActive(o,t),g=o.querySelector("code"),m=p?o:g||o,v=[],y=g.textContent.match(n),b=y?y.length+1:1,A=g&&m!=g?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(o,g):0;h.forEach((function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i;if(!((n=Math.min(b+d,n))i&&r.setAttribute("data-end",String(n)),r.style.top=(i-d-1)*f+A+"px",r.textContent=new Array(n-i+2).join(" \n")}));v.push((function(){r.style.width=o.scrollWidth+"px"})),v.push((function(){m.appendChild(r)}))}}));var P=o.id;if(p&&Prism.util.isActive(o,i)&&P){l(o,i)||v.push((function(){o.classList.add(i)}));var E=parseInt(o.getAttribute("data-start")||"1");s(".line-numbers-rows > span",o).forEach((function(e,t){var i=t+E;e.onclick=function(){var e=P+"."+i;r=!1,location.hash=e,setTimeout((function(){r=!0}),1)}}))}return function(){v.forEach(a)}}};var o=0;Prism.hooks.add("before-sanity-check",(function(e){var t=e.element.parentElement;if(u(t)){var i=0;s(".line-highlight",t).forEach((function(e){i+=e.textContent.length,e.parentNode.removeChild(e)})),i&&/^(?: \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}})),Prism.hooks.add("complete",(function e(i){var n=i.element.parentElement;if(u(n)){clearTimeout(o);var r=Prism.plugins.lineNumbers,s=i.plugins&&i.plugins.lineNumbers;l(n,t)&&r&&!s?Prism.hooks.add("line-numbers",e):(Prism.plugins.lineHighlight.highlightLines(n)(),o=setTimeout(c,1))}})),window.addEventListener("hashchange",c),window.addEventListener("resize",(function(){s("pre").filter(u).map((function(e){return Prism.plugins.lineHighlight.highlightLines(e)})).forEach(a)}))}function s(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function l(e,t){return e.classList.contains(t)}function a(e){e()}function u(e){return!!(e&&/pre/i.test(e.nodeName)&&(e.hasAttribute("data-line")||e.id&&Prism.util.isActive(e,i)))}function c(){var e=location.hash.slice(1);s(".temporary.line-highlight").forEach((function(e){e.parentNode.removeChild(e)}));var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);n&&(n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),r&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}}(); !function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var e="line-numbers",n=/\n(?!$)/g,t=Prism.plugins.lineNumbers={getLine:function(n,t){if("PRE"===n.tagName&&n.classList.contains(e)){var i=n.querySelector(".line-numbers-rows");if(i){var r=parseInt(n.getAttribute("data-start"),10)||1,s=r+(i.children.length-1);ts&&(t=s);var l=t-r;return i.children[l]}}},resize:function(e){r([e])},assumeViewportIndependence:!0},i=void 0;window.addEventListener("resize",(function(){t.assumeViewportIndependence&&i===window.innerWidth||(i=window.innerWidth,r(Array.prototype.slice.call(document.querySelectorAll("pre.line-numbers"))))})),Prism.hooks.add("complete",(function(t){if(t.code){var i=t.element,s=i.parentNode;if(s&&/pre/i.test(s.nodeName)&&!i.querySelector(".line-numbers-rows")&&Prism.util.isActive(i,e)){i.classList.remove(e),s.classList.add(e);var l,o=t.code.match(n),a=o?o.length+1:1,u=new Array(a+1).join("");(l=document.createElement("span")).setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=u,s.hasAttribute("data-start")&&(s.style.counterReset="linenumber "+(parseInt(s.getAttribute("data-start"),10)-1)),t.element.appendChild(l),r([s]),Prism.hooks.run("line-numbers",t)}}})),Prism.hooks.add("line-numbers",(function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}))}function r(e){if(0!=(e=e.filter((function(e){var n,t=(n=e,n?window.getComputedStyle?getComputedStyle(n):n.currentStyle||null:null)["white-space"];return"pre-wrap"===t||"pre-line"===t}))).length){var t=e.map((function(e){var t=e.querySelector("code"),i=e.querySelector(".line-numbers-rows");if(t&&i){var r=e.querySelector(".line-numbers-sizer"),s=t.textContent.split(n);r||((r=document.createElement("span")).className="line-numbers-sizer",t.appendChild(r)),r.innerHTML="0",r.style.display="block";var l=r.getBoundingClientRect().height;return r.innerHTML="",{element:e,lines:s,lineHeights:[],oneLinerHeight:l,sizer:r}}})).filter(Boolean);t.forEach((function(e){var n=e.sizer,t=e.lines,i=e.lineHeights,r=e.oneLinerHeight;i[t.length-1]=void 0,t.forEach((function(e,t){if(e&&e.length>1){var s=n.appendChild(document.createElement("span"));s.style.display="block",s.textContent=e}else i[t]=r}))})),t.forEach((function(e){for(var n=e.sizer,t=e.lineHeights,i=0,r=0;r0?n.fn.concat.apply([],t):t}function F(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function q(t){return t in f?f[t]:f[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function H(t,e){return"number"!=typeof e||c[F(t)]?e:e+"px"}function I(t){var e,n;return u[t]||(e=a.createElement(t),a.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),u[t]=n),u[t]}function V(t){return"children"in t?o.call(t.children):n.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function U(n,i,r){for(e in i)r&&(R(i[e])||A(i[e]))?(R(i[e])&&!R(n[e])&&(n[e]={}),A(i[e])&&!A(n[e])&&(n[e]=[]),U(n[e],i[e],r)):i[e]!==t&&(n[e]=i[e])}function B(t,e){return null==e?n(t):n(t).filter(e)}function J(t,e,n,i){return Z(e)?e.call(t,n,i):e}function X(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function W(e,n){var i=e.className,r=i&&i.baseVal!==t;return n===t?r?i.baseVal:i:void(r?i.baseVal=n:e.className=n)}function Y(t){var e;try{return t?"true"==t||("false"==t?!1:"null"==t?null:/^0/.test(t)||isNaN(e=Number(t))?/^[\[\{]/.test(t)?n.parseJSON(t):t:e):t}catch(i){return t}}function G(t,e){e(t);for(var n in t.childNodes)G(t.childNodes[n],e)}var t,e,n,i,C,N,r=[],o=r.slice,s=r.filter,a=window.document,u={},f={},c={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},l=/^\s*<(\w+|!)[^>]*>/,h=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,p=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,d=/^(?:body|html)$/i,m=/([A-Z])/g,g=["val","css","html","text","data","width","height","offset"],v=["after","prepend","before","append"],y=a.createElement("table"),x=a.createElement("tr"),b={tr:a.createElement("tbody"),tbody:y,thead:y,tfoot:y,td:x,th:x,"*":a.createElement("div")},w=/complete|loaded|interactive/,E=/^[\w-]*$/,j={},T=j.toString,S={},O=a.createElement("div"),P={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},A=Array.isArray||function(t){return t instanceof Array};return S.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var i,r=t.parentNode,o=!r;return o&&(r=O).appendChild(t),i=~S.qsa(r,e).indexOf(t),o&&O.removeChild(t),i},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return s.call(t,function(e,n){return t.indexOf(e)==n})},S.fragment=function(e,i,r){var s,u,f;return h.test(e)&&(s=n(a.createElement(RegExp.$1))),s||(e.replace&&(e=e.replace(p,"<$1>")),i===t&&(i=l.test(e)&&RegExp.$1),i in b||(i="*"),f=b[i],f.innerHTML=""+e,s=n.each(o.call(f.childNodes),function(){f.removeChild(this)})),R(r)&&(u=n(s),n.each(r,function(t,e){g.indexOf(t)>-1?u[t](e):u.attr(t,e)})),s},S.Z=function(t,e){return t=t||[],t.__proto__=n.fn,t.selector=e||"",t},S.isZ=function(t){return t instanceof S.Z},S.init=function(e,i){var r;if(!e)return S.Z();if("string"==typeof e)if(e=e.trim(),"<"==e[0]&&l.test(e))r=S.fragment(e,RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=S.qsa(a,e)}else{if(Z(e))return n(a).ready(e);if(S.isZ(e))return e;if(A(e))r=k(e);else if(D(e))r=[e],e=null;else if(l.test(e))r=S.fragment(e.trim(),RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=S.qsa(a,e)}}return S.Z(r,e)},n=function(t,e){return S.init(t,e)},n.extend=function(t){var e,n=o.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){U(t,n,e)}),t},S.qsa=function(t,e){var n,i="#"==e[0],r=!i&&"."==e[0],s=i||r?e.slice(1):e,a=E.test(s);return _(t)&&a&&i?(n=t.getElementById(s))?[n]:[]:1!==t.nodeType&&9!==t.nodeType?[]:o.call(a&&!i?r?t.getElementsByClassName(s):t.getElementsByTagName(e):t.querySelectorAll(e))},n.contains=function(t,e){return t!==e&&t.contains(e)},n.type=L,n.isFunction=Z,n.isWindow=$,n.isArray=A,n.isPlainObject=R,n.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},n.inArray=function(t,e,n){return r.indexOf.call(e,t,n)},n.camelCase=C,n.trim=function(t){return null==t?"":String.prototype.trim.call(t)},n.uuid=0,n.support={},n.expr={},n.map=function(t,e){var n,r,o,i=[];if(M(t))for(r=0;r=0?e:e+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return r.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return Z(t)?this.not(this.not(t)):n(s.call(this,function(e){return S.matches(e,t)}))},add:function(t,e){return n(N(this.concat(n(t,e))))},is:function(t){return this.length>0&&S.matches(this[0],t)},not:function(e){var i=[];if(Z(e)&&e.call!==t)this.each(function(t){e.call(this,t)||i.push(this)});else{var r="string"==typeof e?this.filter(e):M(e)&&Z(e.item)?o.call(e):n(e);this.forEach(function(t){r.indexOf(t)<0&&i.push(t)})}return n(i)},has:function(t){return this.filter(function(){return D(t)?n.contains(this,t):n(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!D(t)?t:n(t)},last:function(){var t=this[this.length-1];return t&&!D(t)?t:n(t)},find:function(t){var e,i=this;return e="object"==typeof t?n(t).filter(function(){var t=this;return r.some.call(i,function(e){return n.contains(e,t)})}):1==this.length?n(S.qsa(this[0],t)):this.map(function(){return S.qsa(this,t)})},closest:function(t,e){var i=this[0],r=!1;for("object"==typeof t&&(r=n(t));i&&!(r?r.indexOf(i)>=0:S.matches(i,t));)i=i!==e&&!_(i)&&i.parentNode;return n(i)},parents:function(t){for(var e=[],i=this;i.length>0;)i=n.map(i,function(t){return(t=t.parentNode)&&!_(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return B(e,t)},parent:function(t){return B(N(this.pluck("parentNode")),t)},children:function(t){return B(this.map(function(){return V(this)}),t)},contents:function(){return this.map(function(){return o.call(this.childNodes)})},siblings:function(t){return B(this.map(function(t,e){return s.call(V(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return n.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=I(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=Z(t);if(this[0]&&!e)var i=n(t).get(0),r=i.parentNode||this.length>1;return this.each(function(o){n(this).wrapAll(e?t.call(this,o):r?i.cloneNode(!0):i)})},wrapAll:function(t){if(this[0]){n(this[0]).before(t=n(t));for(var e;(e=t.children()).length;)t=e.first();n(t).append(this)}return this},wrapInner:function(t){var e=Z(t);return this.each(function(i){var r=n(this),o=r.contents(),s=e?t.call(this,i):t;o.length?o.wrapAll(s):r.append(s)})},unwrap:function(){return this.parent().each(function(){n(this).replaceWith(n(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(e){return this.each(function(){var i=n(this);(e===t?"none"==i.css("display"):e)?i.show():i.hide()})},prev:function(t){return n(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return n(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0===arguments.length?this.length>0?this[0].innerHTML:null:this.each(function(e){var i=this.innerHTML;n(this).empty().append(J(this,t,e,i))})},text:function(e){return 0===arguments.length?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=e===t?"":""+e})},attr:function(n,i){var r;return"string"==typeof n&&i===t?0==this.length||1!==this[0].nodeType?t:"value"==n&&"INPUT"==this[0].nodeName?this.val():!(r=this[0].getAttribute(n))&&n in this[0]?this[0][n]:r:this.each(function(t){if(1===this.nodeType)if(D(n))for(e in n)X(this,e,n[e]);else X(this,n,J(this,i,t,this.getAttribute(n)))})},removeAttr:function(t){return this.each(function(){1===this.nodeType&&X(this,t)})},prop:function(e,n){return e=P[e]||e,n===t?this[0]&&this[0][e]:this.each(function(t){this[e]=J(this,n,t,this[e])})},data:function(e,n){var i=this.attr("data-"+e.replace(m,"-$1").toLowerCase(),n);return null!==i?Y(i):t},val:function(t){return 0===arguments.length?this[0]&&(this[0].multiple?n(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value):this.each(function(e){this.value=J(this,t,e,this.value)})},offset:function(t){if(t)return this.each(function(e){var i=n(this),r=J(this,t,e,i.offset()),o=i.offsetParent().offset(),s={top:r.top-o.top,left:r.left-o.left};"static"==i.css("position")&&(s.position="relative"),i.css(s)});if(0==this.length)return null;var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(t,i){if(arguments.length<2){var r=this[0],o=getComputedStyle(r,"");if(!r)return;if("string"==typeof t)return r.style[C(t)]||o.getPropertyValue(t);if(A(t)){var s={};return n.each(A(t)?t:[t],function(t,e){s[e]=r.style[C(e)]||o.getPropertyValue(e)}),s}}var a="";if("string"==L(t))i||0===i?a=F(t)+":"+H(t,i):this.each(function(){this.style.removeProperty(F(t))});else for(e in t)t[e]||0===t[e]?a+=F(e)+":"+H(e,t[e])+";":this.each(function(){this.style.removeProperty(F(e))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(n(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?r.some.call(this,function(t){return this.test(W(t))},q(t)):!1},addClass:function(t){return t?this.each(function(e){i=[];var r=W(this),o=J(this,t,e,r);o.split(/\s+/g).forEach(function(t){n(this).hasClass(t)||i.push(t)},this),i.length&&W(this,r+(r?" ":"")+i.join(" "))}):this},removeClass:function(e){return this.each(function(n){return e===t?W(this,""):(i=W(this),J(this,e,n,i).split(/\s+/g).forEach(function(t){i=i.replace(q(t)," ")}),void W(this,i.trim()))})},toggleClass:function(e,i){return e?this.each(function(r){var o=n(this),s=J(this,e,r,W(this));s.split(/\s+/g).forEach(function(e){(i===t?!o.hasClass(e):i)?o.addClass(e):o.removeClass(e)})}):this},scrollTop:function(e){if(this.length){var n="scrollTop"in this[0];return e===t?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=e}:function(){this.scrollTo(this.scrollX,e)})}},scrollLeft:function(e){if(this.length){var n="scrollLeft"in this[0];return e===t?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=e}:function(){this.scrollTo(e,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),i=this.offset(),r=d.test(e[0].nodeName)?{top:0,left:0}:e.offset();return i.top-=parseFloat(n(t).css("margin-top"))||0,i.left-=parseFloat(n(t).css("margin-left"))||0,r.top+=parseFloat(n(e[0]).css("border-top-width"))||0,r.left+=parseFloat(n(e[0]).css("border-left-width"))||0,{top:i.top-r.top,left:i.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||a.body;t&&!d.test(t.nodeName)&&"static"==n(t).css("position");)t=t.offsetParent;return t})}},n.fn.detach=n.fn.remove,["width","height"].forEach(function(e){var i=e.replace(/./,function(t){return t[0].toUpperCase()});n.fn[e]=function(r){var o,s=this[0];return r===t?$(s)?s["inner"+i]:_(s)?s.documentElement["scroll"+i]:(o=this.offset())&&o[e]:this.each(function(t){s=n(this),s.css(e,J(this,r,t,s[e]()))})}}),v.forEach(function(t,e){var i=e%2;n.fn[t]=function(){var t,o,r=n.map(arguments,function(e){return t=L(e),"object"==t||"array"==t||null==e?e:S.fragment(e)}),s=this.length>1;return r.length<1?this:this.each(function(t,a){o=i?a:a.parentNode,a=0==e?a.nextSibling:1==e?a.firstChild:2==e?a:null,r.forEach(function(t){if(s)t=t.cloneNode(!0);else if(!o)return n(t).remove();G(o.insertBefore(t,a),function(t){null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src||window.eval.call(window,t.innerHTML)})})})},n.fn[i?t+"To":"insert"+(e?"Before":"After")]=function(e){return n(e)[t](this),this}}),S.Z.prototype=n.fn,S.uniq=N,S.deserializeValue=Y,n.zepto=S,n}();window.Zepto=Zepto,void 0===window.$&&(window.$=Zepto),function(t){function l(t){return t._zid||(t._zid=e++)}function h(t,e,n,i){if(e=p(e),e.ns)var r=d(e.ns);return(s[l(t)]||[]).filter(function(t){return!(!t||e.e&&t.e!=e.e||e.ns&&!r.test(t.ns)||n&&l(t.fn)!==l(n)||i&&t.sel!=i)})}function p(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function d(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function m(t,e){return t.del&&!u&&t.e in f||!!e}function g(t){return c[t]||u&&f[t]||t}function v(e,i,r,o,a,u,f){var h=l(e),d=s[h]||(s[h]=[]);i.split(/\s/).forEach(function(i){if("ready"==i)return t(document).ready(r);var s=p(i);s.fn=r,s.sel=a,s.e in c&&(r=function(e){var n=e.relatedTarget;return!n||n!==this&&!t.contains(this,n)?s.fn.apply(this,arguments):void 0}),s.del=u;var l=u||r;s.proxy=function(t){if(t=j(t),!t.isImmediatePropagationStopped()){t.data=o;var i=l.apply(e,t._args==n?[t]:[t].concat(t._args));return i===!1&&(t.preventDefault(),t.stopPropagation()),i}},s.i=d.length,d.push(s),"addEventListener"in e&&e.addEventListener(g(s.e),s.proxy,m(s,f))})}function y(t,e,n,i,r){var o=l(t);(e||"").split(/\s/).forEach(function(e){h(t,e,n,i).forEach(function(e){delete s[o][e.i],"removeEventListener"in t&&t.removeEventListener(g(e.e),e.proxy,m(e,r))})})}function j(e,i){return(i||!e.isDefaultPrevented)&&(i||(i=e),t.each(E,function(t,n){var r=i[t];e[t]=function(){return this[n]=x,r&&r.apply(i,arguments)},e[n]=b}),(i.defaultPrevented!==n?i.defaultPrevented:"returnValue"in i?i.returnValue===!1:i.getPreventDefault&&i.getPreventDefault())&&(e.isDefaultPrevented=x)),e}function T(t){var e,i={originalEvent:t};for(e in t)w.test(e)||t[e]===n||(i[e]=t[e]);return j(i,t)}var n,e=1,i=Array.prototype.slice,r=t.isFunction,o=function(t){return"string"==typeof t},s={},a={},u="onfocusin"in window,f={focus:"focusin",blur:"focusout"},c={mouseenter:"mouseover",mouseleave:"mouseout"};a.click=a.mousedown=a.mouseup=a.mousemove="MouseEvents",t.event={add:v,remove:y},t.proxy=function(e,n){if(r(e)){var i=function(){return e.apply(n,arguments)};return i._zid=l(e),i}if(o(n))return t.proxy(e[n],e);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,i){return this.on(t,e,n,i,1)};var x=function(){return!0},b=function(){return!1},w=/^([A-Z]|returnValue$|layer[XY]$)/,E={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,s,a,u,f){var c,l,h=this;return e&&!o(e)?(t.each(e,function(t,e){h.on(t,s,a,e,f)}),h):(o(s)||r(u)||u===!1||(u=a,a=s,s=n),(r(a)||a===!1)&&(u=a,a=n),u===!1&&(u=b),h.each(function(n,r){f&&(c=function(t){return y(r,t.type,u),u.apply(this,arguments)}),s&&(l=function(e){var n,o=t(e.target).closest(s,r).get(0);return o&&o!==r?(n=t.extend(T(e),{currentTarget:o,liveFired:r}),(c||u).apply(o,[n].concat(i.call(arguments,1)))):void 0}),v(r,e,u,a,s,l||c)}))},t.fn.off=function(e,i,s){var a=this;return e&&!o(e)?(t.each(e,function(t,e){a.off(t,i,e)}),a):(o(i)||r(s)||s===!1||(s=i,i=n),s===!1&&(s=b),a.each(function(){y(this,e,s,i)}))},t.fn.trigger=function(e,n){return e=o(e)||t.isPlainObject(e)?t.Event(e):j(e),e._args=n,this.each(function(){"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,n){var i,r;return this.each(function(s,a){i=T(o(e)?t.Event(e):e),i._args=n,i.target=a,t.each(h(a,e.type||e),function(t,e){return r=e.proxy(i),i.isImmediatePropagationStopped()?!1:void 0})}),r},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.trigger(e)}}),["focus","blur"].forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.each(function(){try{this[e]()}catch(t){}}),this}}),t.Event=function(t,e){o(t)||(e=t,t=e.type);var n=document.createEvent(a[t]||"Events"),i=!0;if(e)for(var r in e)"bubbles"==r?i=!!e[r]:n[r]=e[r];return n.initEvent(t,i,!0),j(n)}}(Zepto),function(t){function l(e,n,i){var r=t.Event(n);return t(e).trigger(r,i),!r.isDefaultPrevented()}function h(t,e,i,r){return t.global?l(e||n,i,r):void 0}function p(e){e.global&&0===t.active++&&h(e,null,"ajaxStart")}function d(e){e.global&&!--t.active&&h(e,null,"ajaxStop")}function m(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||h(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void h(e,n,"ajaxSend",[t,e])}function g(t,e,n,i){var r=n.context,o="success";n.success.call(r,t,o,e),i&&i.resolveWith(r,[t,o,e]),h(n,r,"ajaxSuccess",[e,n,t]),y(o,e,n)}function v(t,e,n,i,r){var o=i.context;i.error.call(o,n,e,t),r&&r.rejectWith(o,[n,e,t]),h(i,o,"ajaxError",[n,i,t||e]),y(e,n,i)}function y(t,e,n){var i=n.context;n.complete.call(i,e,t),h(n,i,"ajaxComplete",[e,n]),d(n)}function x(){}function b(t){return t&&(t=t.split(";",2)[0]),t&&(t==f?"html":t==u?"json":s.test(t)?"script":a.test(t)&&"xml")||"text"}function w(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function E(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()||(e.url=w(e.url,e.data),e.data=void 0)}function j(e,n,i,r){return t.isFunction(n)&&(r=i,i=n,n=void 0),t.isFunction(i)||(r=i,i=void 0),{url:e,data:n,success:i,dataType:r}}function S(e,n,i,r){var o,s=t.isArray(n),a=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),r&&(n=i?r:r+"["+(a||"object"==o||"array"==o?n:"")+"]"),!r&&s?e.add(u.name,u.value):"array"==o||!i&&"object"==o?S(e,u,i,n):e.add(n,u)})}var i,r,e=0,n=window.document,o=/)<[^<]*)*<\/script>/gi,s=/^(?:text|application)\/javascript/i,a=/^(?:text|application)\/xml/i,u="application/json",f="text/html",c=/^\s*$/;t.active=0,t.ajaxJSONP=function(i,r){if(!("type"in i))return t.ajax(i);var f,h,o=i.jsonpCallback,s=(t.isFunction(o)?o():o)||"jsonp"+ ++e,a=n.createElement("script"),u=window[s],c=function(e){t(a).triggerHandler("error",e||"abort")},l={abort:c};return r&&r.promise(l),t(a).on("load error",function(e,n){clearTimeout(h),t(a).off().remove(),"error"!=e.type&&f?g(f[0],l,i,r):v(null,n||"error",l,i,r),window[s]=u,f&&t.isFunction(u)&&u(f[0]),u=f=void 0}),m(l,i)===!1?(c("abort"),l):(window[s]=function(){f=arguments},a.src=i.url.replace(/\?(.+)=\?/,"?$1="+s),n.head.appendChild(a),i.timeout>0&&(h=setTimeout(function(){c("timeout")},i.timeout)),l)},t.ajaxSettings={type:"GET",beforeSend:x,success:x,error:x,complete:x,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:u,xml:"application/xml, text/xml",html:f,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},t.ajax=function(e){var n=t.extend({},e||{}),o=t.Deferred&&t.Deferred();for(i in t.ajaxSettings)void 0===n[i]&&(n[i]=t.ajaxSettings[i]);p(n),n.crossDomain||(n.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(n.url)&&RegExp.$2!=window.location.host),n.url||(n.url=window.location.toString()),E(n),n.cache===!1&&(n.url=w(n.url,"_="+Date.now()));var s=n.dataType,a=/\?.+=\?/.test(n.url);if("jsonp"==s||a)return a||(n.url=w(n.url,n.jsonp?n.jsonp+"=?":n.jsonp===!1?"":"callback=?")),t.ajaxJSONP(n,o);var j,u=n.accepts[s],f={},l=function(t,e){f[t.toLowerCase()]=[t,e]},h=/^([\w-]+:)\/\//.test(n.url)?RegExp.$1:window.location.protocol,d=n.xhr(),y=d.setRequestHeader;if(o&&o.promise(d),n.crossDomain||l("X-Requested-With","XMLHttpRequest"),l("Accept",u||"*/*"),(u=n.mimeType||u)&&(u.indexOf(",")>-1&&(u=u.split(",",2)[0]),d.overrideMimeType&&d.overrideMimeType(u)),(n.contentType||n.contentType!==!1&&n.data&&"GET"!=n.type.toUpperCase())&&l("Content-Type",n.contentType||"application/x-www-form-urlencoded"),n.headers)for(r in n.headers)l(r,n.headers[r]);if(d.setRequestHeader=l,d.onreadystatechange=function(){if(4==d.readyState){d.onreadystatechange=x,clearTimeout(j);var e,i=!1;if(d.status>=200&&d.status<300||304==d.status||0==d.status&&"file:"==h){s=s||b(n.mimeType||d.getResponseHeader("content-type")),e=d.responseText;try{"script"==s?(1,eval)(e):"xml"==s?e=d.responseXML:"json"==s&&(e=c.test(e)?null:t.parseJSON(e))}catch(r){i=r}i?v(i,"parsererror",d,n,o):g(e,d,n,o)}else v(d.statusText||null,d.status?"error":"abort",d,n,o)}},m(d,n)===!1)return d.abort(),v(null,"abort",d,n,o),d;if(n.xhrFields)for(r in n.xhrFields)d[r]=n.xhrFields[r];var T="async"in n?n.async:!0;d.open(n.type,n.url,T,n.username,n.password);for(r in f)y.apply(d,f[r]);return n.timeout>0&&(j=setTimeout(function(){d.onreadystatechange=x,d.abort(),v(null,"timeout",d,n,o)},n.timeout)),d.send(n.data?n.data:null),d},t.get=function(){return t.ajax(j.apply(null,arguments))},t.post=function(){var e=j.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=j.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,i){if(!this.length)return this;var a,r=this,s=e.split(/\s/),u=j(e,n,i),f=u.success;return s.length>1&&(u.url=s[0],a=s[1]),u.success=function(e){r.html(a?t("
").html(e.replace(o,"")).find(a):e),f&&f.apply(r,arguments)},t.ajax(u),this};var T=encodeURIComponent;t.param=function(t,e){var n=[];return n.add=function(t,e){this.push(T(t)+"="+T(e))},S(n,t,e),n.join("&").replace(/%20/g,"+")}}(Zepto),function(t){t.fn.serializeArray=function(){var n,e=[];return t([].slice.call(this.get(0).elements)).each(function(){n=t(this);var i=n.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=i&&"reset"!=i&&"button"!=i&&("radio"!=i&&"checkbox"!=i||this.checked)&&e.push({name:n.attr("name"),value:n.val()})}),e},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(e)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(Zepto),function(t){"__proto__"in{}||t.extend(t.zepto,{Z:function(e,n){return e=e||[],t.extend(e,t.fn),e.selector=n||"",e.__Z=!0,e},isZ:function(e){return"array"===t.type(e)&&"__Z"in e}});try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;window.getComputedStyle=function(t){try{return n(t)}catch(e){return null}}}}(Zepto); PKcRZX2foo"Whoops/Resources/js/whoops.base.jsnu[Zepto(function($) { var $leftPanel = $('.left-panel'); var $frameContainer = $('.frames-container'); var $appFramesTab = $('#application-frames-tab'); var $allFramesTab = $('#all-frames-tab'); var $container = $('.details-container'); var $activeLine = $frameContainer.find('.frame.active'); var $activeFrame = $container.find('.frame-code.active'); var $ajaxEditors = $('.editor-link[data-ajax]'); var $header = $('header'); $header.on('mouseenter', function () { if ($header.find('.exception').height() >= 145) { $header.addClass('header-expand'); } }); $header.on('mouseleave', function () { $header.removeClass('header-expand'); }); /* * add prettyprint classes to our current active codeblock * run prettyPrint() to highlight the active code * scroll to the line when prettyprint is done * highlight the current line */ var renderCurrentCodeblock = function(id) { Prism.highlightAll(); highlightCurrentLine(); } /* * Highlight the active and neighboring lines for the current frame * Adjust the offset to make sure that line is veritcally centered */ var highlightCurrentLine = function() { // We show more code than needed, purely for proper syntax highlighting // Let’s hide a big chunk of that code and then scroll the remaining block $activeFrame.find('.code-block').first().css({ maxHeight: 345, overflow: 'hidden', }); var line = $activeFrame.find('.code-block .line-highlight').first()[0]; line.scrollIntoView(); line.parentElement.scrollTop -= 180; $container.scrollTop(0); } /* * click handler for loading codeblocks */ $frameContainer.on('click', '.frame', function() { var $this = $(this); var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1]; var $codeFrame = $('#frame-code-' + id); if ($codeFrame) { $activeLine.removeClass('active'); $activeFrame.removeClass('active'); $this.addClass('active'); $codeFrame.addClass('active'); $activeLine = $this; $activeFrame = $codeFrame; renderCurrentCodeblock(id); } }); var clipboard = new Clipboard('.clipboard'); var showTooltip = function(elem, msg) { elem.classList.add('tooltipped', 'tooltipped-s'); elem.setAttribute('aria-label', msg); }; clipboard.on('success', function(e) { e.clearSelection(); showTooltip(e.trigger, 'Copied!'); }); clipboard.on('error', function(e) { showTooltip(e.trigger, fallbackMessage(e.action)); }); var btn = document.querySelector('.clipboard'); btn.addEventListener('mouseleave', function(e) { e.currentTarget.classList.remove('tooltipped', 'tooltipped-s'); e.currentTarget.removeAttribute('aria-label'); }); function fallbackMessage(action) { var actionMsg = ''; var actionKey = (action === 'cut' ? 'X' : 'C'); if (/Mac/i.test(navigator.userAgent)) { actionMsg = 'Press ⌘-' + actionKey + ' to ' + action; } else { actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action; } return actionMsg; } function scrollIntoView($node, $parent) { var nodeOffset = $node.offset(); var nodeTop = nodeOffset.top; var nodeBottom = nodeTop + nodeOffset.height; var parentScrollTop = $parent.scrollTop(); var parentHeight = $parent.height(); if (nodeTop < 0) { $parent.scrollTop(parentScrollTop + nodeTop); } else if (nodeBottom > parentHeight) { $parent.scrollTop(parentScrollTop + nodeBottom - parentHeight); } } $(document).on('keydown', function(e) { var applicationFrames = $frameContainer.hasClass('frames-container-application'), frameClass = applicationFrames ? '.frame.frame-application' : '.frame'; if(e.ctrlKey || e.which === 74 || e.which === 75) { // CTRL+Arrow-UP/k and Arrow-Down/j support: // 1) select the next/prev element // 2) make sure the newly selected element is within the view-scope // 3) focus the (right) container, so arrow-up/down (without ctrl) scroll the details if (e.which === 38 /* arrow up */ || e.which === 75 /* k */) { $activeLine.prev(frameClass).click(); scrollIntoView($activeLine, $leftPanel); $container.focus(); e.preventDefault(); } else if (e.which === 40 /* arrow down */ || e.which === 74 /* j */) { $activeLine.next(frameClass).click(); scrollIntoView($activeLine, $leftPanel); $container.focus(); e.preventDefault(); } } else if (e.which == 78 /* n */) { if ($appFramesTab.length) { setActiveFramesTab($('.frames-tab:not(.frames-tab-active)')); } } }); // Render late enough for highlightCurrentLine to be ready renderCurrentCodeblock(); // Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API) $ajaxEditors.on('click', function(e){ e.preventDefault(); $.get(this.href); }); // Symfony VarDumper: Close the by default expanded objects $('.sf-dump-expanded') .removeClass('sf-dump-expanded') .addClass('sf-dump-compact'); $('.sf-dump-toggle span').html('▶'); // Make the given frames-tab active function setActiveFramesTab($tab) { $tab.addClass('frames-tab-active'); if ($tab.attr('id') == 'application-frames-tab') { $frameContainer.addClass('frames-container-application'); $allFramesTab.removeClass('frames-tab-active'); } else { $frameContainer.removeClass('frames-container-application'); $appFramesTab.removeClass('frames-tab-active'); } } $('a.frames-tab').on('click', function(e) { e.preventDefault(); setActiveFramesTab($(this)); }); }); PKcRZL"L"$Whoops/Resources/js/clipboard.min.jsnu[/*! * clipboard.js v1.5.3 * https://zenorocha.github.io/clipboard.js * * Licensed MIT © Zeno Rocha */ !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ar;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n */ namespace Whoops; use InvalidArgumentException; use Throwable; use Whoops\Exception\ErrorException; use Whoops\Handler\CallbackHandler; use Whoops\Handler\Handler; use Whoops\Handler\HandlerInterface; use Whoops\Inspector\CallableInspectorFactory; use Whoops\Inspector\InspectorFactory; use Whoops\Inspector\InspectorFactoryInterface; use Whoops\Inspector\InspectorInterface; use Whoops\Util\Misc; use Whoops\Util\SystemFacade; final class Run implements RunInterface { /** * @var bool */ private $isRegistered; /** * @var bool */ private $allowQuit = true; /** * @var bool */ private $sendOutput = true; /** * @var integer|false */ private $sendHttpCode = 500; /** * @var integer|false */ private $sendExitCode = 1; /** * @var HandlerInterface[] */ private $handlerStack = []; /** * @var array * @psalm-var list */ private $silencedPatterns = []; /** * @var SystemFacade */ private $system; /** * In certain scenarios, like in shutdown handler, we can not throw exceptions. * * @var bool */ private $canThrowExceptions = true; /** * The inspector factory to create inspectors. * * @var InspectorFactoryInterface */ private $inspectorFactory; /** * @var array */ private $frameFilters = []; public function __construct(SystemFacade $system = null) { $this->system = $system ?: new SystemFacade; $this->inspectorFactory = new InspectorFactory(); } /** * Explicitly request your handler runs as the last of all currently registered handlers. * * @param callable|HandlerInterface $handler * * @return Run */ public function appendHandler($handler) { array_unshift($this->handlerStack, $this->resolveHandler($handler)); return $this; } /** * Explicitly request your handler runs as the first of all currently registered handlers. * * @param callable|HandlerInterface $handler * * @return Run */ public function prependHandler($handler) { return $this->pushHandler($handler); } /** * Register your handler as the last of all currently registered handlers (to be executed first). * Prefer using appendHandler and prependHandler for clarity. * * @param callable|HandlerInterface $handler * * @return Run * * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface. */ public function pushHandler($handler) { $this->handlerStack[] = $this->resolveHandler($handler); return $this; } /** * Removes and returns the last handler pushed to the handler stack. * * @see Run::removeFirstHandler(), Run::removeLastHandler() * * @return HandlerInterface|null */ public function popHandler() { return array_pop($this->handlerStack); } /** * Removes the first handler. * * @return void */ public function removeFirstHandler() { array_pop($this->handlerStack); } /** * Removes the last handler. * * @return void */ public function removeLastHandler() { array_shift($this->handlerStack); } /** * Returns an array with all handlers, in the order they were added to the stack. * * @return array */ public function getHandlers() { return $this->handlerStack; } /** * Clears all handlers in the handlerStack, including the default PrettyPage handler. * * @return Run */ public function clearHandlers() { $this->handlerStack = []; return $this; } public function getFrameFilters() { return $this->frameFilters; } public function clearFrameFilters() { $this->frameFilters = []; return $this; } /** * Registers this instance as an error handler. * * @return Run */ public function register() { if (!$this->isRegistered) { // Workaround PHP bug 42098 // https://bugs.php.net/bug.php?id=42098 class_exists("\\Whoops\\Exception\\ErrorException"); class_exists("\\Whoops\\Exception\\FrameCollection"); class_exists("\\Whoops\\Exception\\Frame"); class_exists("\\Whoops\\Exception\\Inspector"); class_exists("\\Whoops\\Inspector\\InspectorFactory"); $this->system->setErrorHandler([$this, self::ERROR_HANDLER]); $this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]); $this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]); $this->isRegistered = true; } return $this; } /** * Unregisters all handlers registered by this Whoops\Run instance. * * @return Run */ public function unregister() { if ($this->isRegistered) { $this->system->restoreExceptionHandler(); $this->system->restoreErrorHandler(); $this->isRegistered = false; } return $this; } /** * Should Whoops allow Handlers to force the script to quit? * * @param bool|int $exit * * @return bool */ public function allowQuit($exit = null) { if (func_num_args() == 0) { return $this->allowQuit; } return $this->allowQuit = (bool) $exit; } /** * Silence particular errors in particular files. * * @param array|string $patterns List or a single regex pattern to match. * @param int $levels Defaults to E_STRICT | E_DEPRECATED. * * @return Run */ public function silenceErrorsInPaths($patterns, $levels = 10240) { $this->silencedPatterns = array_merge( $this->silencedPatterns, array_map( function ($pattern) use ($levels) { return [ "pattern" => $pattern, "levels" => $levels, ]; }, (array) $patterns ) ); return $this; } /** * Returns an array with silent errors in path configuration. * * @return array */ public function getSilenceErrorsInPaths() { return $this->silencedPatterns; } /** * Should Whoops send HTTP error code to the browser if possible? * Whoops will by default send HTTP code 500, but you may wish to * use 502, 503, or another 5xx family code. * * @param bool|int $code * * @return int|false * * @throws InvalidArgumentException */ public function sendHttpCode($code = null) { if (func_num_args() == 0) { return $this->sendHttpCode; } if (!$code) { return $this->sendHttpCode = false; } if ($code === true) { $code = 500; } if ($code < 400 || 600 <= $code) { throw new InvalidArgumentException( "Invalid status code '$code', must be 4xx or 5xx" ); } return $this->sendHttpCode = $code; } /** * Should Whoops exit with a specific code on the CLI if possible? * Whoops will exit with 1 by default, but you can specify something else. * * @param int $code * * @return int * * @throws InvalidArgumentException */ public function sendExitCode($code = null) { if (func_num_args() == 0) { return $this->sendExitCode; } if ($code < 0 || 255 <= $code) { throw new InvalidArgumentException( "Invalid status code '$code', must be between 0 and 254" ); } return $this->sendExitCode = (int) $code; } /** * Should Whoops push output directly to the client? * If this is false, output will be returned by handleException. * * @param bool|int $send * * @return bool */ public function writeToOutput($send = null) { if (func_num_args() == 0) { return $this->sendOutput; } return $this->sendOutput = (bool) $send; } /** * Handles an exception, ultimately generating a Whoops error page. * * @param Throwable $exception * * @return string Output generated by handlers. */ public function handleException($exception) { // Walk the registered handlers in the reverse order // they were registered, and pass off the exception $inspector = $this->getInspector($exception); // Capture output produced while handling the exception, // we might want to send it straight away to the client, // or return it silently. $this->system->startOutputBuffering(); // Just in case there are no handlers: $handlerResponse = null; $handlerContentType = null; try { foreach (array_reverse($this->handlerStack) as $handler) { $handler->setRun($this); $handler->setInspector($inspector); $handler->setException($exception); // The HandlerInterface does not require an Exception passed to handle() // and neither of our bundled handlers use it. // However, 3rd party handlers may have already relied on this parameter, // and removing it would be possibly breaking for users. $handlerResponse = $handler->handle($exception); // Collect the content type for possible sending in the headers. $handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null; if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) { // The Handler has handled the exception in some way, and // wishes to quit execution (Handler::QUIT), or skip any // other handlers (Handler::LAST_HANDLER). If $this->allowQuit // is false, Handler::QUIT behaves like Handler::LAST_HANDLER break; } } $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit(); } finally { $output = $this->system->cleanOutputBuffer(); } // If we're allowed to, send output generated by handlers directly // to the output, otherwise, and if the script doesn't quit, return // it so that it may be used by the caller if ($this->writeToOutput()) { // @todo Might be able to clean this up a bit better if ($willQuit) { // Cleanup all other output buffers before sending our output: while ($this->system->getOutputBufferLevel() > 0) { $this->system->endOutputBuffering(); } // Send any headers if needed: if (Misc::canSendHeaders() && $handlerContentType) { header("Content-Type: {$handlerContentType}"); } } $this->writeToOutputNow($output); } if ($willQuit) { // HHVM fix for https://github.com/facebook/hhvm/issues/4055 $this->system->flushOutputBuffer(); $this->system->stopExecution( $this->sendExitCode() ); } return $output; } /** * Converts generic PHP errors to \ErrorException instances, before passing them off to be handled. * * This method MUST be compatible with set_error_handler. * * @param int $level * @param string $message * @param string|null $file * @param int|null $line * * @return bool * * @throws ErrorException */ public function handleError($level, $message, $file = null, $line = null) { if ($level & $this->system->getErrorReportingLevel()) { foreach ($this->silencedPatterns as $entry) { $pathMatches = (bool) preg_match($entry["pattern"], $file); $levelMatches = $level & $entry["levels"]; if ($pathMatches && $levelMatches) { // Ignore the error, abort handling // See https://github.com/filp/whoops/issues/418 return true; } } // XXX we pass $level for the "code" param only for BC reasons. // see https://github.com/filp/whoops/issues/267 $exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line); if ($this->canThrowExceptions) { throw $exception; } else { $this->handleException($exception); } // Do not propagate errors which were already handled by Whoops. return true; } // Propagate error to the next handler, allows error_get_last() to // work on silenced errors. return false; } /** * Special case to deal with Fatal errors and the like. * * @return void */ public function handleShutdown() { // If we reached this step, we are in shutdown handler. // An exception thrown in a shutdown handler will not be propagated // to the exception handler. Pass that information along. $this->canThrowExceptions = false; $error = $this->system->getLastError(); if ($error && Misc::isLevelFatal($error['type'])) { // If there was a fatal error, // it was not handled in handleError yet. $this->allowQuit = false; $this->handleError( $error['type'], $error['message'], $error['file'], $error['line'] ); } } /** * @param InspectorFactoryInterface $factory * * @return void */ public function setInspectorFactory(InspectorFactoryInterface $factory) { $this->inspectorFactory = $factory; } public function addFrameFilter($filterCallback) { if (!is_callable($filterCallback)) { throw new \InvalidArgumentException(sprintf( "A frame filter must be of type callable, %s type given.", gettype($filterCallback) )); } $this->frameFilters[] = $filterCallback; return $this; } /** * @param Throwable $exception * * @return InspectorInterface */ private function getInspector($exception) { return $this->inspectorFactory->create($exception); } /** * Resolves the giving handler. * * @param callable|HandlerInterface $handler * * @return HandlerInterface * * @throws InvalidArgumentException */ private function resolveHandler($handler) { if (is_callable($handler)) { $handler = new CallbackHandler($handler); } if (!$handler instanceof HandlerInterface) { throw new InvalidArgumentException( "Handler must be a callable, or instance of " . "Whoops\\Handler\\HandlerInterface" ); } return $handler; } /** * Echo something to the browser. * * @param string $output * * @return Run */ private function writeToOutputNow($output) { if ($this->sendHttpCode() && Misc::canSendHeaders()) { $this->system->setHttpResponseCode( $this->sendHttpCode() ); } echo $output; return $this; } } PKZ wwLoggerTrait.phpnu[log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string|\Stringable $message * @param array $context * * @return void */ public function alert(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string|\Stringable $message * @param array $context * * @return void */ public function critical(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string|\Stringable $message * @param array $context * * @return void */ public function error(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string|\Stringable $message * @param array $context * * @return void */ public function warning(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string|\Stringable $message * @param array $context * * @return void */ public function notice(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string|\Stringable $message * @param array $context * * @return void */ public function info(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string|\Stringable $message * @param array $context * * @return void */ public function debug(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::DEBUG, $message, $context); } /** * Logs with an arbitrary level. * * @param mixed $level * @param string|\Stringable $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ abstract public function log($level, string|\Stringable $message, array $context = []): void; } PKZK;NullLogger.phpnu[logger) { }` * blocks. */ class NullLogger extends AbstractLogger { /** * Logs with an arbitrary level. * * @param mixed $level * @param string|\Stringable $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ public function log($level, string|\Stringable $message, array $context = []): void { // noop } } PKZ~//LoggerAwareInterface.phpnu[LoggerAwareTrait.phpnu[logger = $logger; } } PK ]Zs≱Console/InstallCommand.phpnu[option('with')) { $services = $this->option('with') == 'none' ? [] : explode(',', $this->option('with')); } elseif ($this->option('no-interaction')) { $services = $this->defaultServices; } else { $services = $this->gatherServicesWithSymfonyMenu(); } if ($invalidServices = array_diff($services, $this->services)) { $this->error('Invalid services ['.implode(',', $invalidServices).'].'); return 1; } $this->buildDockerCompose($services); $this->replaceEnvVariables($services); $this->configurePhpUnit(); if ($this->option('devcontainer')) { $this->installDevContainer(); } $this->info('Sail scaffolding installed successfully.'); $this->prepareInstallation($services); } } PK ]Z.^Console/AddCommand.phpnu[argument('services')) { $services = $this->argument('services') == 'none' ? [] : explode(',', $this->argument('services')); } elseif ($this->option('no-interaction')) { $services = $this->defaultServices; } else { $services = $this->gatherServicesWithSymfonyMenu(); } if ($invalidServices = array_diff($services, $this->services)) { $this->error('Invalid services ['.implode(',', $invalidServices).'].'); return 1; } $this->buildDockerCompose($services); $this->replaceEnvVariables($services); $this->configurePhpUnit(); $this->info('Additional Sail services installed successfully.'); $this->prepareInstallation($services); } } PK ]Z^Console/PublishCommand.phpnu[call('vendor:publish', ['--tag' => 'sail-docker']); file_put_contents( $this->laravel->basePath('docker-compose.yml'), str_replace( [ './vendor/laravel/sail/runtimes/8.2', './vendor/laravel/sail/runtimes/8.1', './vendor/laravel/sail/runtimes/8.0', './vendor/laravel/sail/runtimes/7.4', ], [ './docker/8.2', './docker/8.1', './docker/8.0', './docker/7.4', ], file_get_contents($this->laravel->basePath('docker-compose.yml')) ) ); } } PK ]ZU##7Console/Concerns/InteractsWithDockerComposeServices.phpnu[ */ protected $services = [ 'mysql', 'pgsql', 'mariadb', 'redis', 'memcached', 'meilisearch', 'minio', 'mailpit', 'selenium', 'soketi', ]; /** * The default services used when the user chooses non-interactive mode. * * @var string[] */ protected $defaultServices = ['mysql', 'redis', 'selenium', 'mailpit']; /** * Gather the desired Sail services using a Symfony menu. * * @return array */ protected function gatherServicesWithSymfonyMenu() { return $this->choice('Which services would you like to install?', $this->services, 0, null, true); } /** * Build the Docker Compose file. * * @param array $services * @return void */ protected function buildDockerCompose(array $services) { $composePath = base_path('docker-compose.yml'); $compose = file_exists($composePath) ? Yaml::parseFile($composePath) : Yaml::parse(file_get_contents(__DIR__ . '/../../../stubs/docker-compose.stub')); // Adds the new services as dependencies of the laravel.test service... if (! array_key_exists('laravel.test', $compose['services'])) { $this->warn('Couldn\'t find the laravel.test service. Make sure you add ['.implode(',', $services).'] to the depends_on config.'); } else { $compose['services']['laravel.test']['depends_on'] = collect($compose['services']['laravel.test']['depends_on'] ?? []) ->merge($services) ->unique() ->values() ->all(); } // Add the services to the docker-compose.yml... collect($services) ->filter(function ($service) use ($compose) { return ! array_key_exists($service, $compose['services'] ?? []); })->each(function ($service) use (&$compose) { $compose['services'][$service] = Yaml::parseFile(__DIR__ . "/../../../stubs/{$service}.stub")[$service]; }); // Merge volumes... collect($services) ->filter(function ($service) { return in_array($service, ['mysql', 'pgsql', 'mariadb', 'redis', 'meilisearch', 'minio']); })->filter(function ($service) use ($compose) { return ! array_key_exists($service, $compose['volumes'] ?? []); })->each(function ($service) use (&$compose) { $compose['volumes']["sail-{$service}"] = ['driver' => 'local']; }); // If the list of volumes is empty, we can remove it... if (empty($compose['volumes'])) { unset($compose['volumes']); } // Replace Selenium with ARM base container on Apple Silicon... if (in_array('selenium', $services) && in_array(php_uname('m'), ['arm64', 'aarch64'])) { $compose['services']['selenium']['image'] = 'seleniarm/standalone-chromium'; } file_put_contents($this->laravel->basePath('docker-compose.yml'), Yaml::dump($compose, Yaml::DUMP_OBJECT_AS_MAP)); } /** * Replace the Host environment variables in the app's .env file. * * @param array $services * @return void */ protected function replaceEnvVariables(array $services) { $environment = file_get_contents($this->laravel->basePath('.env')); if (in_array('pgsql', $services)) { $environment = str_replace('DB_CONNECTION=mysql', "DB_CONNECTION=pgsql", $environment); $environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=pgsql", $environment); $environment = str_replace('DB_PORT=3306', "DB_PORT=5432", $environment); } elseif (in_array('mariadb', $services)) { $environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=mariadb", $environment); } else { $environment = str_replace('DB_HOST=127.0.0.1', "DB_HOST=mysql", $environment); } $environment = str_replace('DB_USERNAME=root', "DB_USERNAME=sail", $environment); $environment = preg_replace("/DB_PASSWORD=(.*)/", "DB_PASSWORD=password", $environment); if (in_array('memcached', $services)) { $environment = str_replace('MEMCACHED_HOST=127.0.0.1', 'MEMCACHED_HOST=memcached', $environment); } if (in_array('redis', $services)) { $environment = str_replace('REDIS_HOST=127.0.0.1', 'REDIS_HOST=redis', $environment); } if (in_array('meilisearch', $services)) { $environment .= "\nSCOUT_DRIVER=meilisearch"; $environment .= "\nMEILISEARCH_HOST=http://meilisearch:7700\n"; } if (in_array('soketi', $services)) { $environment = preg_replace("/^BROADCAST_DRIVER=(.*)/m", "BROADCAST_DRIVER=pusher", $environment); $environment = preg_replace("/^PUSHER_APP_ID=(.*)/m", "PUSHER_APP_ID=app-id", $environment); $environment = preg_replace("/^PUSHER_APP_KEY=(.*)/m", "PUSHER_APP_KEY=app-key", $environment); $environment = preg_replace("/^PUSHER_APP_SECRET=(.*)/m", "PUSHER_APP_SECRET=app-secret", $environment); $environment = preg_replace("/^PUSHER_HOST=(.*)/m", "PUSHER_HOST=soketi", $environment); $environment = preg_replace("/^PUSHER_PORT=(.*)/m", "PUSHER_PORT=6001", $environment); $environment = preg_replace("/^PUSHER_SCHEME=(.*)/m", "PUSHER_SCHEME=http", $environment); $environment = preg_replace("/^VITE_PUSHER_HOST=(.*)/m", "VITE_PUSHER_HOST=localhost", $environment); } if (in_array('mailpit', $services)) { $environment = preg_replace("/^MAIL_HOST=(.*)/m", "MAIL_HOST=mailpit", $environment); } file_put_contents($this->laravel->basePath('.env'), $environment); } /** * Configure PHPUnit to use the dedicated testing database. * * @return void */ protected function configurePhpUnit() { if (! file_exists($path = $this->laravel->basePath('phpunit.xml'))) { $path = $this->laravel->basePath('phpunit.xml.dist'); } $phpunit = file_get_contents($path); $phpunit = preg_replace('/^.*DB_CONNECTION.*\n/m', '', $phpunit); $phpunit = str_replace('', '', $phpunit); file_put_contents($this->laravel->basePath('phpunit.xml'), $phpunit); } /** * Install the devcontainer.json configuration file. * * @return void */ protected function installDevContainer() { if (! is_dir($this->laravel->basePath('.devcontainer'))) { mkdir($this->laravel->basePath('.devcontainer'), 0755, true); } file_put_contents( $this->laravel->basePath('.devcontainer/devcontainer.json'), file_get_contents(__DIR__.'/../../../stubs/devcontainer.stub') ); $environment = file_get_contents($this->laravel->basePath('.env')); $environment .= "\nWWWGROUP=1000"; $environment .= "\nWWWUSER=1000\n"; file_put_contents($this->laravel->basePath('.env'), $environment); } /** * Prepare the installation by pulling and building any necessary images. * * @param array $services * @return void */ protected function prepareInstallation($services) { // Ensure docker is installed... if ($this->runCommands(['docker info > /dev/null 2>&1']) !== 0) { return; } if (count($services) > 0) { $status = $this->runCommands([ './vendor/bin/sail pull '.implode(' ', $services), ]); if ($status === 0) { $this->info('Sail images installed successfully.'); } } $status = $this->runCommands([ './vendor/bin/sail build', ]); if ($status === 0) { $this->info('Sail build successful.'); } } /** * Run the given commands. * * @param array $commands * @return int */ protected function runCommands($commands) { $process = Process::fromShellCommandline(implode(' && ', $commands), null, null, null, null); if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { try { $process->setTty(true); } catch (\RuntimeException $e) { $this->output->writeln(' WARN '.$e->getMessage().PHP_EOL); } } return $process->run(function ($type, $line) { $this->output->write(' '.$line); }); } } PK ]ZxsaXXSailServiceProvider.phpnu[registerCommands(); $this->configurePublishing(); } /** * Register the console commands for the package. * * @return void */ protected function registerCommands() { if ($this->app->runningInConsole()) { $this->commands([ InstallCommand::class, AddCommand::class, PublishCommand::class, ]); } } /** * Configure publishing for the package. * * @return void */ protected function configurePublishing() { if ($this->app->runningInConsole()) { $this->publishes([ __DIR__ . '/../runtimes' => $this->app->basePath('docker'), ], ['sail', 'sail-docker']); $this->publishes([ __DIR__ . '/../bin/sail' => $this->app->basePath('sail'), ], ['sail', 'sail-bin']); } } /** * Get the services provided by the provider. * * @return array */ public function provides() { return [ InstallCommand::class, PublishCommand::class, ]; } } PKZiSet.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * A set is a collection that contains no duplicate elements. * * Great care must be exercised if mutable objects are used as set elements. * The behavior of a set is not specified if the value of an object is changed * in a manner that affects equals comparisons while the object is an element in * the set. * * Example usage: * * ``` php * $foo = new \My\Foo(); * $set = new Set(\My\Foo::class); * * $set->add($foo); // returns TRUE, the element don't exists * $set->add($foo); // returns FALSE, the element already exists * * $bar = new \My\Foo(); * $set->add($bar); // returns TRUE, $bar !== $foo * ``` * * @template T * @extends AbstractSet */ class Set extends AbstractSet { /** * The type of elements stored in this set * * A set's type is immutable. For this reason, this property is private. * * @var string */ private $setType; /** * Constructs a set object of the specified type, optionally with the * specified data. * * @param string $setType The type (FQCN) associated with this set. * @param array $data The initial items to store in the set. */ public function __construct(string $setType, array $data = []) { $this->setType = $setType; parent::__construct($data); } public function getType(): string { return $this->setType; } } PKZ)AbstractArray.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use ArrayIterator; use Traversable; use function serialize; use function unserialize; /** * This class provides a basic implementation of `ArrayInterface`, to minimize * the effort required to implement this interface. * * @template T * @implements ArrayInterface */ abstract class AbstractArray implements ArrayInterface { /** * The items of this array. * * @var array */ protected $data = []; /** * Constructs a new array object. * * @param array $data The initial items to add to this array. */ public function __construct(array $data = []) { // Invoke offsetSet() for each value added; in this way, sub-classes // may provide additional logic about values added to the array object. foreach ($data as $key => $value) { $this[$key] = $value; } } /** * Returns an iterator for this array. * * @link http://php.net/manual/en/iteratoraggregate.getiterator.php IteratorAggregate::getIterator() * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->data); } /** * Returns `true` if the given offset exists in this array. * * @link http://php.net/manual/en/arrayaccess.offsetexists.php ArrayAccess::offsetExists() * * @param array-key $offset The offset to check. */ public function offsetExists($offset): bool { return isset($this->data[$offset]); } /** * Returns the value at the specified offset. * * @link http://php.net/manual/en/arrayaccess.offsetget.php ArrayAccess::offsetGet() * * @param array-key $offset The offset for which a value should be returned. * * @return T|null the value stored at the offset, or null if the offset * does not exist. * * @psalm-suppress InvalidAttribute */ #[\ReturnTypeWillChange] // phpcs:ignore public function offsetGet($offset) { return $this->data[$offset] ?? null; } /** * Sets the given value to the given offset in the array. * * @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet() * * @param array-key|null $offset The offset to set. If `null`, the value may be * set at a numerically-indexed offset. * @param T $value The value to set at the given offset. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offsetSet($offset, $value): void { if ($offset === null) { $this->data[] = $value; } else { $this->data[$offset] = $value; } } /** * Removes the given offset and its value from the array. * * @link http://php.net/manual/en/arrayaccess.offsetunset.php ArrayAccess::offsetUnset() * * @param array-key $offset The offset to remove from the array. */ public function offsetUnset($offset): void { unset($this->data[$offset]); } /** * Returns a serialized string representation of this array object. * * @deprecated The Serializable interface will go away in PHP 9. * * @link http://php.net/manual/en/serializable.serialize.php Serializable::serialize() * * @return string a PHP serialized string. */ public function serialize(): string { return serialize($this->data); } /** * Returns data suitable for PHP serialization. * * @link https://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.serialize * @link https://www.php.net/serialize * * @return array */ public function __serialize(): array { return $this->data; } /** * Converts a serialized string representation into an instance object. * * @deprecated The Serializable interface will go away in PHP 9. * * @link http://php.net/manual/en/serializable.unserialize.php Serializable::unserialize() * * @param string $serialized A PHP serialized string to unserialize. * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, ['allowed_classes' => false]); $this->data = $data; } /** * Adds unserialized data to the object. * * @param array $data */ public function __unserialize(array $data): void { $this->data = $data; } /** * Returns the number of items in this array. * * @link http://php.net/manual/en/countable.count.php Countable::count() */ public function count(): int { return count($this->data); } public function clear(): void { $this->data = []; } /** * @inheritDoc */ public function toArray(): array { return $this->data; } public function isEmpty(): bool { return count($this->data) === 0; } } PKZKDCollectionInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * A collection represents a group of objects, known as its elements. * * Some collections allow duplicate elements and others do not. Some are ordered * and others unordered. * * @template T * @extends ArrayInterface */ interface CollectionInterface extends ArrayInterface { /** * Ascending sort type. */ public const SORT_ASC = 'asc'; /** * Descending sort type. */ public const SORT_DESC = 'desc'; /** * Ensures that this collection contains the specified element (optional * operation). * * Returns `true` if this collection changed as a result of the call. * (Returns `false` if this collection does not permit duplicates and * already contains the specified element.) * * Collections that support this operation may place limitations on what * elements may be added to this collection. In particular, some * collections will refuse to add `null` elements, and others will impose * restrictions on the type of elements that may be added. Collection * classes should clearly specify in their documentation any restrictions * on what elements may be added. * * If a collection refuses to add a particular element for any reason other * than that it already contains the element, it must throw an exception * (rather than returning `false`). This preserves the invariant that a * collection always contains the specified element after this call returns. * * @param T $element The element to add to the collection. * * @return bool `true` if this collection changed as a result of the call. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function add($element): bool; /** * Returns `true` if this collection contains the specified element. * * @param T $element The element to check whether the collection contains. * @param bool $strict Whether to perform a strict type check on the value. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function contains($element, bool $strict = true): bool; /** * Returns the type associated with this collection. */ public function getType(): string; /** * Removes a single instance of the specified element from this collection, * if it is present. * * @param T $element The element to remove from the collection. * * @return bool `true` if an element was removed as a result of this call. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function remove($element): bool; /** * Returns the values from the given property or method. * * @param string $propertyOrMethod The property or method name to filter by. * * @return list */ public function column(string $propertyOrMethod): array; /** * Returns the first item of the collection. * * @return T */ public function first(); /** * Returns the last item of the collection. * * @return T */ public function last(); /** * Sort the collection by a property or method with the given sort order. * * This will always leave the original collection untouched and will return * a new one. * * @param string $propertyOrMethod The property or method to sort by. * @param string $order The sort order for the resulting collection (one of * this interface's `SORT_*` constants). * * @return CollectionInterface */ public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): self; /** * Filter out items of the collection which don't match the criteria of * given callback. * * This will always leave the original collection untouched and will return * a new one. * * See the {@link http://php.net/manual/en/function.array-filter.php PHP array_filter() documentation} * for examples of how the `$callback` parameter works. * * @param callable(T):bool $callback A callable to use for filtering elements. * * @return CollectionInterface */ public function filter(callable $callback): self; /** * Create a new collection where items match the criteria of given callback. * * This will always leave the original collection untouched and will return * a new one. * * @param string $propertyOrMethod The property or method to evaluate. * @param mixed $value The value to match. * * @return CollectionInterface */ public function where(string $propertyOrMethod, $value): self; /** * Apply a given callback method on each item of the collection. * * This will always leave the original collection untouched. The new * collection is created by mapping the callback to each item of the * original collection. * * See the {@link http://php.net/manual/en/function.array-map.php PHP array_map() documentation} * for examples of how the `$callback` parameter works. * * @param callable(T):TCallbackReturn $callback A callable to apply to each * item of the collection. * * @return CollectionInterface * * @template TCallbackReturn */ public function map(callable $callback): self; /** * Create a new collection with divergent items between current and given * collection. * * @param CollectionInterface $other The collection to check for divergent * items. * * @return CollectionInterface */ public function diff(CollectionInterface $other): self; /** * Create a new collection with intersecting item between current and given * collection. * * @param CollectionInterface $other The collection to check for * intersecting items. * * @return CollectionInterface */ public function intersect(CollectionInterface $other): self; /** * Merge current items and items of given collections into a new one. * * @param CollectionInterface ...$collections The collections to merge. * * @return CollectionInterface */ public function merge(CollectionInterface ...$collections): self; } PKZɢ; Queue.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Exception\NoSuchElementException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueToStringTrait; /** * This class provides a basic implementation of `QueueInterface`, to minimize * the effort required to implement this interface. * * @template T * @extends AbstractArray * @implements QueueInterface */ class Queue extends AbstractArray implements QueueInterface { use TypeTrait; use ValueToStringTrait; /** * The type of elements stored in this queue. * * A queue's type is immutable once it is set. For this reason, this * property is set private. * * @var string */ private $queueType; /** * The index of the head of the queue. * * @var int */ protected $index = 0; /** * Constructs a queue object of the specified type, optionally with the * specified data. * * @param string $queueType The type (FQCN) associated with this queue. * @param array $data The initial items to store in the collection. */ public function __construct(string $queueType, array $data = []) { $this->queueType = $queueType; parent::__construct($data); } /** * {@inheritDoc} * * Since arbitrary offsets may not be manipulated in a queue, this method * serves only to fulfill the `ArrayAccess` interface requirements. It is * invoked by other operations when adding values to the queue. */ public function offsetSet($offset, $value): void { if ($this->checkType($this->getType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value) ); } $this->data[] = $value; } /** * @inheritDoc */ public function add($element): bool { $this[] = $element; return true; } /** * @inheritDoc */ public function element() { $element = $this->peek(); if ($element === null) { throw new NoSuchElementException( 'Can\'t return element from Queue. Queue is empty.' ); } return $element; } /** * @inheritDoc */ public function offer($element): bool { try { return $this->add($element); } catch (InvalidArgumentException $e) { return false; } } /** * @inheritDoc */ public function peek() { if ($this->count() === 0) { return null; } return $this[$this->index]; } /** * @inheritDoc */ public function poll() { if ($this->count() === 0) { return null; } $head = $this[$this->index]; unset($this[$this->index]); $this->index++; return $head; } /** * @inheritDoc */ public function remove() { $head = $this->poll(); if ($head === null) { throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); } return $head; } public function getType(): string { return $this->queueType; } } PKZ1Tool/TypeTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Tool; use function is_array; use function is_bool; use function is_callable; use function is_float; use function is_int; use function is_numeric; use function is_object; use function is_resource; use function is_scalar; use function is_string; /** * Provides functionality to check values for specific types. */ trait TypeTrait { /** * Returns `true` if value is of the specified type. * * @param string $type The type to check the value against. * @param mixed $value The value to check. */ protected function checkType(string $type, $value): bool { switch ($type) { case 'array': return is_array($value); case 'bool': case 'boolean': return is_bool($value); case 'callable': return is_callable($value); case 'float': case 'double': return is_float($value); case 'int': case 'integer': return is_int($value); case 'null': return $value === null; case 'numeric': return is_numeric($value); case 'object': return is_object($value); case 'resource': return is_resource($value); case 'scalar': return is_scalar($value); case 'string': return is_string($value); case 'mixed': return true; default: return $value instanceof $type; } } } PKZ@W W Tool/ValueToStringTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Tool; use DateTimeInterface; use function get_class; use function get_resource_type; use function is_array; use function is_bool; use function is_callable; use function is_resource; use function is_scalar; /** * Provides functionality to express a value as string */ trait ValueToStringTrait { /** * Returns a string representation of the value. * * - null value: `'NULL'` * - boolean: `'TRUE'`, `'FALSE'` * - array: `'Array'` * - scalar: converted-value * - resource: `'(type resource #number)'` * - object with `__toString()`: result of `__toString()` * - object DateTime: ISO 8601 date * - object: `'(className Object)'` * - anonymous function: same as object * * @param mixed $value the value to return as a string. */ protected function toolValueToString($value): string { // null if ($value === null) { return 'NULL'; } // boolean constants if (is_bool($value)) { return $value ? 'TRUE' : 'FALSE'; } // array if (is_array($value)) { return 'Array'; } // scalar types (integer, float, string) if (is_scalar($value)) { return (string) $value; } // resource if (is_resource($value)) { return '(' . get_resource_type($value) . ' resource #' . (int) $value . ')'; } // If we don't know what it is, use var_export(). if (!is_object($value)) { return '(' . var_export($value, true) . ')'; } // From here, $value should be an object. // __toString() is implemented if (is_callable([$value, '__toString'])) { return (string) $value->__toString(); } // object of type \DateTime if ($value instanceof DateTimeInterface) { return $value->format('c'); } // unknown type return '(' . get_class($value) . ' Object)'; } } PKZX'Tool/ValueExtractorTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Tool; use Ramsey\Collection\Exception\ValueExtractionException; use function get_class; use function method_exists; use function property_exists; use function sprintf; /** * Provides functionality to extract the value of a property or method from an object. */ trait ValueExtractorTrait { /** * Extracts the value of the given property or method from the object. * * @param mixed $object The object to extract the value from. * @param string $propertyOrMethod The property or method for which the * value should be extracted. * * @return mixed the value extracted from the specified property or method. * * @throws ValueExtractionException if the method or property is not defined. */ protected function extractValue($object, string $propertyOrMethod) { if (!is_object($object)) { throw new ValueExtractionException('Unable to extract a value from a non-object'); } if (property_exists($object, $propertyOrMethod)) { return $object->$propertyOrMethod; } if (method_exists($object, $propertyOrMethod)) { return $object->{$propertyOrMethod}(); } throw new ValueExtractionException( sprintf('Method or property "%s" not defined in %s', $propertyOrMethod, get_class($object)) ); } } PKZ)^ K))DoubleEndedQueueInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\NoSuchElementException; /** * A linear collection that supports element insertion and removal at both ends. * * Most `DoubleEndedQueueInterface` implementations place no fixed limits on the * number of elements they may contain, but this interface supports * capacity-restricted double-ended queues as well as those with no fixed size * limit. * * This interface defines methods to access the elements at both ends of the * double-ended queue. Methods are provided to insert, remove, and examine the * element. Each of these methods exists in two forms: one throws an exception * if the operation fails, the other returns a special value (either `null` or * `false`, depending on the operation). The latter form of the insert operation * is designed specifically for use with capacity-restricted implementations; in * most implementations, insert operations cannot fail. * * The twelve methods described above are summarized in the following table: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary of DoubleEndedQueueInterface methods
First Element (Head)Last Element (Tail)
Throws exceptionSpecial valueThrows exceptionSpecial value
InsertaddFirst()offerFirst()addLast()offerLast()
RemoveremoveFirst()pollFirst()removeLast()pollLast()
ExaminefirstElement()peekFirst()lastElement()peekLast()
* * This interface extends the `QueueInterface`. When a double-ended queue is * used as a queue, FIFO (first-in-first-out) behavior results. Elements are * added at the end of the double-ended queue and removed from the beginning. * The methods inherited from the `QueueInterface` are precisely equivalent to * `DoubleEndedQueueInterface` methods as indicated in the following table: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Comparison of QueueInterface and DoubleEndedQueueInterface methods
QueueInterface MethodDoubleEndedQueueInterface Method
add()addLast()
offer()offerLast()
remove()removeFirst()
poll()pollFirst()
element()firstElement()
peek()peekFirst()
* * Double-ended queues can also be used as LIFO (last-in-first-out) stacks. When * a double-ended queue is used as a stack, elements are pushed and popped from * the beginning of the double-ended queue. Stack concepts are precisely * equivalent to `DoubleEndedQueueInterface` methods as indicated in the table * below: * * * * * * * * * * * * * * * * * * * * * * * *
Comparison of stack concepts and DoubleEndedQueueInterface methods
Stack conceptDoubleEndedQueueInterface Method
pushaddFirst()
popremoveFirst()
peekpeekFirst()
* * Note that the `peek()` method works equally well when a double-ended queue is * used as a queue or a stack; in either case, elements are drawn from the * beginning of the double-ended queue. * * While `DoubleEndedQueueInterface` implementations are not strictly required * to prohibit the insertion of `null` elements, they are strongly encouraged to * do so. Users of any `DoubleEndedQueueInterface` implementations that do allow * `null` elements are strongly encouraged *not* to take advantage of the * ability to insert nulls. This is so because `null` is used as a special * return value by various methods to indicated that the double-ended queue is * empty. * * @template T * @extends QueueInterface */ interface DoubleEndedQueueInterface extends QueueInterface { /** * Inserts the specified element at the front of this queue if it is * possible to do so immediately without violating capacity restrictions. * * When using a capacity-restricted double-ended queue, it is generally * preferable to use the `offerFirst()` method. * * @param T $element The element to add to the front of this queue. * * @return bool `true` if this queue changed as a result of the call. * * @throws \RuntimeException if a queue refuses to add a particular element * for any reason other than that it already contains the element. * Implementations should use a more-specific exception that extends * `\RuntimeException`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function addFirst($element): bool; /** * Inserts the specified element at the end of this queue if it is possible * to do so immediately without violating capacity restrictions. * * When using a capacity-restricted double-ended queue, it is generally * preferable to use the `offerLast()` method. * * This method is equivalent to `add()`. * * @param T $element The element to add to the end of this queue. * * @return bool `true` if this queue changed as a result of the call. * * @throws \RuntimeException if a queue refuses to add a particular element * for any reason other than that it already contains the element. * Implementations should use a more-specific exception that extends * `\RuntimeException`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function addLast($element): bool; /** * Inserts the specified element at the front of this queue if it is * possible to do so immediately without violating capacity restrictions. * * When using a capacity-restricted queue, this method is generally * preferable to `addFirst()`, which can fail to insert an element only by * throwing an exception. * * @param T $element The element to add to the front of this queue. * * @return bool `true` if the element was added to this queue, else `false`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offerFirst($element): bool; /** * Inserts the specified element at the end of this queue if it is possible * to do so immediately without violating capacity restrictions. * * When using a capacity-restricted queue, this method is generally * preferable to `addLast()` which can fail to insert an element only by * throwing an exception. * * @param T $element The element to add to the end of this queue. * * @return bool `true` if the element was added to this queue, else `false`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offerLast($element): bool; /** * Retrieves and removes the head of this queue. * * This method differs from `pollFirst()` only in that it throws an * exception if this queue is empty. * * @return T the first element in this queue. * * @throws NoSuchElementException if this queue is empty. */ public function removeFirst(); /** * Retrieves and removes the tail of this queue. * * This method differs from `pollLast()` only in that it throws an exception * if this queue is empty. * * @return T the last element in this queue. * * @throws NoSuchElementException if this queue is empty. */ public function removeLast(); /** * Retrieves and removes the head of this queue, or returns `null` if this * queue is empty. * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function pollFirst(); /** * Retrieves and removes the tail of this queue, or returns `null` if this * queue is empty. * * @return T|null the tail of this queue, or `null` if this queue is empty. */ public function pollLast(); /** * Retrieves, but does not remove, the head of this queue. * * This method differs from `peekFirst()` only in that it throws an * exception if this queue is empty. * * @return T the head of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function firstElement(); /** * Retrieves, but does not remove, the tail of this queue. * * This method differs from `peekLast()` only in that it throws an exception * if this queue is empty. * * @return T the tail of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function lastElement(); /** * Retrieves, but does not remove, the head of this queue, or returns `null` * if this queue is empty. * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function peekFirst(); /** * Retrieves, but does not remove, the tail of this queue, or returns `null` * if this queue is empty. * * @return T|null the tail of this queue, or `null` if this queue is empty. */ public function peekLast(); } PKZN$Exception/NoSuchElementException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to access an element that does not exist. */ class NoSuchElementException extends \RuntimeException { } PKZU"$  +Exception/UnsupportedOperationException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown to indicate that the requested operation is not supported. */ class UnsupportedOperationException extends \RuntimeException { } PKZGF  )Exception/CollectionMismatchException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to operate on collections of differing types. */ class CollectionMismatchException extends \RuntimeException { } PKZ- "Exception/OutOfBoundsException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to access an element out of the range of the collection. */ class OutOfBoundsException extends \OutOfBoundsException { } PKZ C~'Exception/InvalidSortOrderException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to use a sort order that is not recognized. */ class InvalidSortOrderException extends \RuntimeException { } PKZ|f&Exception/ValueExtractionException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown when attempting to extract a value for a method or property that does not exist. */ class ValueExtractionException extends \RuntimeException { } PKZ:&Exception/InvalidArgumentException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Exception; /** * Thrown to indicate an argument is not of the expected type. */ class InvalidArgumentException extends \InvalidArgumentException { } PKZ@ lMMAbstractSet.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * This class contains the basic implementation of a collection that does not * allow duplicated values (a set), to minimize the effort required to implement * this specific type of collection. * * @template T * @extends AbstractCollection */ abstract class AbstractSet extends AbstractCollection { /** * @inheritDoc */ public function add($element): bool { if ($this->contains($element)) { return false; } return parent::add($element); } /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($this->contains($value)) { return; } parent::offsetSet($offset, $value); } } PKZ;I( ( Collection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * A collection represents a group of objects. * * Each object in the collection is of a specific, defined type. * * This is a direct implementation of `CollectionInterface`, provided for * the sake of convenience. * * Example usage: * * ``` php * $collection = new \Ramsey\Collection\Collection('My\\Foo'); * $collection->add(new \My\Foo()); * $collection->add(new \My\Foo()); * * foreach ($collection as $foo) { * // Do something with $foo * } * ``` * * It is preferable to subclass `AbstractCollection` to create your own typed * collections. For example: * * ``` php * namespace My\Foo; * * class FooCollection extends \Ramsey\Collection\AbstractCollection * { * public function getType() * { * return 'My\\Foo'; * } * } * ``` * * And then use it similarly to the earlier example: * * ``` php * $fooCollection = new \My\Foo\FooCollection(); * $fooCollection->add(new \My\Foo()); * $fooCollection->add(new \My\Foo()); * * foreach ($fooCollection as $foo) { * // Do something with $foo * } * ``` * * The benefit with this approach is that you may do type-checking on the * collection object: * * ``` php * if ($collection instanceof \My\Foo\FooCollection) { * // the collection is a collection of My\Foo objects * } * ``` * * @template T * @extends AbstractCollection */ class Collection extends AbstractCollection { /** * The type of elements stored in this collection. * * A collection's type is immutable once it is set. For this reason, this * property is set private. * * @var string */ private $collectionType; /** * Constructs a collection object of the specified type, optionally with the * specified data. * * @param string $collectionType The type (FQCN) associated with this * collection. * @param array $data The initial items to store in the collection. */ public function __construct(string $collectionType, array $data = []) { $this->collectionType = $collectionType; parent::__construct($data); } public function getType(): string { return $this->collectionType; } } PKZhDoubleEndedQueue.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Exception\NoSuchElementException; /** * This class provides a basic implementation of `DoubleEndedQueueInterface`, to * minimize the effort required to implement this interface. * * @template T * @extends Queue * @implements DoubleEndedQueueInterface */ class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface { /** * Index of the last element in the queue. * * @var int */ private $tail = -1; /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($this->checkType($this->getType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value) ); } $this->tail++; $this->data[$this->tail] = $value; } /** * @inheritDoc */ public function addFirst($element): bool { if ($this->checkType($this->getType(), $element) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($element) ); } $this->index--; $this->data[$this->index] = $element; return true; } /** * @inheritDoc */ public function addLast($element): bool { return $this->add($element); } /** * @inheritDoc */ public function offerFirst($element): bool { try { return $this->addFirst($element); } catch (InvalidArgumentException $e) { return false; } } /** * @inheritDoc */ public function offerLast($element): bool { return $this->offer($element); } /** * @inheritDoc */ public function removeFirst() { return $this->remove(); } /** * @inheritDoc */ public function removeLast() { $tail = $this->pollLast(); if ($tail === null) { throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); } return $tail; } /** * @inheritDoc */ public function pollFirst() { return $this->poll(); } /** * @inheritDoc */ public function pollLast() { if ($this->count() === 0) { return null; } $tail = $this[$this->tail]; unset($this[$this->tail]); $this->tail--; return $tail; } /** * @inheritDoc */ public function firstElement() { return $this->element(); } /** * @inheritDoc */ public function lastElement() { if ($this->count() === 0) { throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.'); } return $this->data[$this->tail]; } /** * @inheritDoc */ public function peekFirst() { return $this->peek(); } /** * @inheritDoc */ public function peekLast() { if ($this->count() === 0) { return null; } return $this->data[$this->tail]; } } PKZ㸿 Map/AbstractMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\AbstractArray; use Ramsey\Collection\Exception\InvalidArgumentException; use function array_key_exists; use function array_keys; use function in_array; /** * This class provides a basic implementation of `MapInterface`, to minimize the * effort required to implement this interface. * * @template T * @extends AbstractArray * @implements MapInterface */ abstract class AbstractMap extends AbstractArray implements MapInterface { /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($offset === null) { throw new InvalidArgumentException( 'Map elements are key/value pairs; a key must be provided for ' . 'value ' . var_export($value, true) ); } $this->data[$offset] = $value; } /** * @inheritDoc */ public function containsKey($key): bool { return array_key_exists($key, $this->data); } /** * @inheritDoc */ public function containsValue($value): bool { return in_array($value, $this->data, true); } /** * @inheritDoc */ public function keys(): array { return array_keys($this->data); } /** * @inheritDoc */ public function get($key, $defaultValue = null) { if (!$this->containsKey($key)) { return $defaultValue; } return $this[$key]; } /** * @inheritDoc */ public function put($key, $value) { $previousValue = $this->get($key); $this[$key] = $value; return $previousValue; } /** * @inheritDoc */ public function putIfAbsent($key, $value) { $currentValue = $this->get($key); if ($currentValue === null) { $this[$key] = $value; } return $currentValue; } /** * @inheritDoc */ public function remove($key) { $previousValue = $this->get($key); unset($this[$key]); return $previousValue; } /** * @inheritDoc */ public function removeIf($key, $value): bool { if ($this->get($key) === $value) { unset($this[$key]); return true; } return false; } /** * @inheritDoc */ public function replace($key, $value) { $currentValue = $this->get($key); if ($this->containsKey($key)) { $this[$key] = $value; } return $currentValue; } /** * @inheritDoc */ public function replaceIf($key, $oldValue, $newValue): bool { if ($this->get($key) === $oldValue) { $this[$key] = $newValue; return true; } return false; } } PKZ:(  Map/NamedParameterMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueToStringTrait; use function array_combine; use function array_key_exists; use function is_int; /** * `NamedParameterMap` represents a mapping of values to a set of named keys * that may optionally be typed * * @extends AbstractMap */ class NamedParameterMap extends AbstractMap { use TypeTrait; use ValueToStringTrait; /** * Named parameters defined for this map. * * @var array */ protected $namedParameters; /** * Constructs a new `NamedParameterMap`. * * @param array $namedParameters The named parameters defined for this map. * @param array $data An initial set of data to set on this map. */ public function __construct(array $namedParameters, array $data = []) { $this->namedParameters = $this->filterNamedParameters($namedParameters); parent::__construct($data); } /** * Returns named parameters set for this `NamedParameterMap`. * * @return array */ public function getNamedParameters(): array { return $this->namedParameters; } /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($offset === null) { throw new InvalidArgumentException( 'Map elements are key/value pairs; a key must be provided for ' . 'value ' . var_export($value, true) ); } if (!array_key_exists($offset, $this->namedParameters)) { throw new InvalidArgumentException( 'Attempting to set value for unconfigured parameter \'' . $offset . '\'' ); } if ($this->checkType($this->namedParameters[$offset], $value) === false) { throw new InvalidArgumentException( 'Value for \'' . $offset . '\' must be of type ' . $this->namedParameters[$offset] . '; value is ' . $this->toolValueToString($value) ); } $this->data[$offset] = $value; } /** * Given an array of named parameters, constructs a proper mapping of * named parameters to types. * * @param array $namedParameters The named parameters to filter. * * @return array */ protected function filterNamedParameters(array $namedParameters): array { $names = []; $types = []; foreach ($namedParameters as $key => $value) { if (is_int($key)) { $names[] = $value; $types[] = 'mixed'; } else { $names[] = $key; $types[] = $value; } } return array_combine($names, $types) ?: []; } } PKZ =T%%Map/AssociativeArrayMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; /** * `AssociativeArrayMap` represents a standard associative array object. * * @template T * @extends AbstractMap */ class AssociativeArrayMap extends AbstractMap { } PKZ`!  Map/MapInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\ArrayInterface; /** * An object that maps keys to values. * * A map cannot contain duplicate keys; each key can map to at most one value. * * @template T * @extends ArrayInterface */ interface MapInterface extends ArrayInterface { /** * Returns `true` if this map contains a mapping for the specified key. * * @param array-key $key The key to check in the map. */ public function containsKey($key): bool; /** * Returns `true` if this map maps one or more keys to the specified value. * * This performs a strict type check on the value. * * @param T $value The value to check in the map. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function containsValue($value): bool; /** * Return an array of the keys contained in this map. * * @return list */ public function keys(): array; /** * Returns the value to which the specified key is mapped, `null` if this * map contains no mapping for the key, or (optionally) `$defaultValue` if * this map contains no mapping for the key. * * @param array-key $key The key to return from the map. * @param T|null $defaultValue The default value to use if `$key` is not found. * * @return T|null the value or `null` if the key could not be found. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function get($key, $defaultValue = null); /** * Associates the specified value with the specified key in this map. * * If the map previously contained a mapping for the key, the old value is * replaced by the specified value. * * @param array-key $key The key to put or replace in the map. * @param T $value The value to store at `$key`. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function put($key, $value); /** * Associates the specified value with the specified key in this map only if * it is not already set. * * If there is already a value associated with `$key`, this returns that * value without replacing it. * * @param array-key $key The key to put in the map. * @param T $value The value to store at `$key`. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function putIfAbsent($key, $value); /** * Removes the mapping for a key from this map if it is present. * * @param array-key $key The key to remove from the map. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function remove($key); /** * Removes the entry for the specified key only if it is currently mapped to * the specified value. * * This performs a strict type check on the value. * * @param array-key $key The key to remove from the map. * @param T $value The value to match. * * @return bool true if the value was removed. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function removeIf($key, $value): bool; /** * Replaces the entry for the specified key only if it is currently mapped * to some value. * * @param array-key $key The key to replace. * @param T $value The value to set at `$key`. * * @return T|null the previous value associated with key, or `null` if * there was no mapping for `$key`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function replace($key, $value); /** * Replaces the entry for the specified key only if currently mapped to the * specified value. * * This performs a strict type check on the value. * * @param array-key $key The key to remove from the map. * @param T $oldValue The value to match. * @param T $newValue The value to use as a replacement. * * @return bool true if the value was replaced. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function replaceIf($key, $oldValue, $newValue): bool; } PKZdʼn Map/TypedMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\Tool\TypeTrait; /** * A `TypedMap` represents a map of elements where key and value are typed. * * Each element is identified by a key with defined type and a value of defined * type. The keys of the map must be unique. The values on the map can be= * repeated but each with its own different key. * * The most common case is to use a string type key, but it's not limited to * this type of keys. * * This is a direct implementation of `TypedMapInterface`, provided for the sake * of convenience. * * Example usage: * * ```php * $map = new TypedMap('string', Foo::class); * $map['x'] = new Foo(); * foreach ($map as $key => $value) { * // do something with $key, it will be a Foo::class * } * * // this will throw an exception since key must be string * $map[10] = new Foo(); * * // this will throw an exception since value must be a Foo * $map['bar'] = 'bar'; * * // initialize map with contents * $map = new TypedMap('string', Foo::class, [ * new Foo(), new Foo(), new Foo() * ]); * ``` * * It is preferable to subclass `AbstractTypedMap` to create your own typed map * implementation: * * ```php * class FooTypedMap extends AbstractTypedMap * { * public function getKeyType() * { * return 'int'; * } * * public function getValueType() * { * return Foo::class; * } * } * ``` * * … but you also may use the `TypedMap` class: * * ```php * class FooTypedMap extends TypedMap * { * public function __constructor(array $data = []) * { * parent::__construct('int', Foo::class, $data); * } * } * ``` * * @template K * @template T * @extends AbstractTypedMap */ class TypedMap extends AbstractTypedMap { use TypeTrait; /** * The data type of keys stored in this collection. * * A map key's type is immutable once it is set. For this reason, this * property is set private. * * @var string data type of the map key. */ private $keyType; /** * The data type of values stored in this collection. * * A map value's type is immutable once it is set. For this reason, this * property is set private. * * @var string data type of the map value. */ private $valueType; /** * Constructs a map object of the specified key and value types, * optionally with the specified data. * * @param string $keyType The data type of the map's keys. * @param string $valueType The data type of the map's values. * @param array $data The initial data to set for this map. */ public function __construct(string $keyType, string $valueType, array $data = []) { $this->keyType = $keyType; $this->valueType = $valueType; /** @psalm-suppress MixedArgumentTypeCoercion */ parent::__construct($data); } public function getKeyType(): string { return $this->keyType; } public function getValueType(): string { return $this->valueType; } } PKZRMap/AbstractTypedMap.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueToStringTrait; /** * This class provides a basic implementation of `TypedMapInterface`, to * minimize the effort required to implement this interface. * * @template K * @template T * @extends AbstractMap * @implements TypedMapInterface */ abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface { use TypeTrait; use ValueToStringTrait; /** * @param K|null $offset * @param T $value * * @inheritDoc * * @psalm-suppress MoreSpecificImplementedParamType */ public function offsetSet($offset, $value): void { if ($offset === null) { throw new InvalidArgumentException( 'Map elements are key/value pairs; a key must be provided for ' . 'value ' . var_export($value, true) ); } if ($this->checkType($this->getKeyType(), $offset) === false) { throw new InvalidArgumentException( 'Key must be of type ' . $this->getKeyType() . '; key is ' . $this->toolValueToString($offset) ); } if ($this->checkType($this->getValueType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getValueType() . '; value is ' . $this->toolValueToString($value) ); } /** @psalm-suppress MixedArgumentTypeCoercion */ parent::offsetSet($offset, $value); } } PKZMap/TypedMapInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection\Map; /** * A `TypedMapInterface` represents a map of elements where key and value are * typed. * * @template T * @extends MapInterface */ interface TypedMapInterface extends MapInterface { /** * Return the type used on the key. */ public function getKeyType(): string; /** * Return the type forced on the values. */ public function getValueType(): string; } PKZVVQueueInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Ramsey\Collection\Exception\NoSuchElementException; /** * A queue is a collection in which the entities in the collection are kept in * order. * * The principal operations on the queue are the addition of entities to the end * (tail), also known as *enqueue*, and removal of entities from the front * (head), also known as *dequeue*. This makes the queue a first-in-first-out * (FIFO) data structure. * * Besides basic array operations, queues provide additional insertion, * extraction, and inspection operations. Each of these methods exists in two * forms: one throws an exception if the operation fails, the other returns a * special value (either `null` or `false`, depending on the operation). The * latter form of the insert operation is designed specifically for use with * capacity-restricted `QueueInterface` implementations; in most * implementations, insert operations cannot fail. * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary of QueueInterface methods
Throws exceptionReturns special value
Insertadd()offer()
Removeremove()poll()
Examineelement()peek()
* * Queues typically, but do not necessarily, order elements in a FIFO * (first-in-first-out) manner. Among the exceptions are priority queues, which * order elements according to a supplied comparator, or the elements' natural * ordering, and LIFO queues (or stacks) which order the elements LIFO * (last-in-first-out). Whatever the ordering used, the head of the queue is * that element which would be removed by a call to remove() or poll(). In a * FIFO queue, all new elements are inserted at the tail of the queue. Other * kinds of queues may use different placement rules. Every `QueueInterface` * implementation must specify its ordering properties. * * The `offer()` method inserts an element if possible, otherwise returning * `false`. This differs from the `add()` method, which can fail to add an * element only by throwing an unchecked exception. The `offer()` method is * designed for use when failure is a normal, rather than exceptional * occurrence, for example, in fixed-capacity (or "bounded") queues. * * The `remove()` and `poll()` methods remove and return the head of the queue. * Exactly which element is removed from the queue is a function of the queue's * ordering policy, which differs from implementation to implementation. The * `remove()` and `poll()` methods differ only in their behavior when the queue * is empty: the `remove()` method throws an exception, while the `poll()` * method returns `null`. * * The `element()` and `peek()` methods return, but do not remove, the head of * the queue. * * `QueueInterface` implementations generally do not allow insertion of `null` * elements, although some implementations do not prohibit insertion of `null`. * Even in the implementations that permit it, `null` should not be inserted * into a queue, as `null` is also used as a special return value by the * `poll()` method to indicate that the queue contains no elements. * * @template T * @extends ArrayInterface */ interface QueueInterface extends ArrayInterface { /** * Ensures that this queue contains the specified element (optional * operation). * * Returns `true` if this queue changed as a result of the call. (Returns * `false` if this queue does not permit duplicates and already contains the * specified element.) * * Queues that support this operation may place limitations on what elements * may be added to this queue. In particular, some queues will refuse to add * `null` elements, and others will impose restrictions on the type of * elements that may be added. Queue classes should clearly specify in their * documentation any restrictions on what elements may be added. * * If a queue refuses to add a particular element for any reason other than * that it already contains the element, it must throw an exception (rather * than returning `false`). This preserves the invariant that a queue always * contains the specified element after this call returns. * * @see self::offer() * * @param T $element The element to add to this queue. * * @return bool `true` if this queue changed as a result of the call. * * @throws \RuntimeException if a queue refuses to add a particular element * for any reason other than that it already contains the element. * Implementations should use a more-specific exception that extends * `\RuntimeException`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function add($element): bool; /** * Retrieves, but does not remove, the head of this queue. * * This method differs from `peek()` only in that it throws an exception if * this queue is empty. * * @see self::peek() * * @return T the head of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function element(); /** * Inserts the specified element into this queue if it is possible to do so * immediately without violating capacity restrictions. * * When using a capacity-restricted queue, this method is generally * preferable to `add()`, which can fail to insert an element only by * throwing an exception. * * @see self::add() * * @param T $element The element to add to this queue. * * @return bool `true` if the element was added to this queue, else `false`. */ // phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint public function offer($element): bool; /** * Retrieves, but does not remove, the head of this queue, or returns `null` * if this queue is empty. * * @see self::element() * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function peek(); /** * Retrieves and removes the head of this queue, or returns `null` * if this queue is empty. * * @see self::remove() * * @return T|null the head of this queue, or `null` if this queue is empty. */ public function poll(); /** * Retrieves and removes the head of this queue. * * This method differs from `poll()` only in that it throws an exception if * this queue is empty. * * @see self::poll() * * @return T the head of this queue. * * @throws NoSuchElementException if this queue is empty. */ public function remove(); /** * Returns the type associated with this queue. */ public function getType(): string; } PKZIIArrayInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use ArrayAccess; use Countable; use IteratorAggregate; use Serializable; /** * `ArrayInterface` provides traversable array functionality to data types. * * @template T * @extends ArrayAccess * @extends IteratorAggregate */ interface ArrayInterface extends ArrayAccess, Countable, IteratorAggregate, Serializable { /** * Removes all items from this array. */ public function clear(): void; /** * Returns a native PHP array representation of this array object. * * @return array */ public function toArray(): array; /** * Returns `true` if this array is empty. */ public function isEmpty(): bool; } PKZKSGenericArray.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; /** * `GenericArray` represents a standard array object. * * @extends AbstractArray */ class GenericArray extends AbstractArray { } PKZF~M#M#AbstractCollection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Collection; use Closure; use Ramsey\Collection\Exception\CollectionMismatchException; use Ramsey\Collection\Exception\InvalidArgumentException; use Ramsey\Collection\Exception\InvalidSortOrderException; use Ramsey\Collection\Exception\OutOfBoundsException; use Ramsey\Collection\Tool\TypeTrait; use Ramsey\Collection\Tool\ValueExtractorTrait; use Ramsey\Collection\Tool\ValueToStringTrait; use function array_filter; use function array_map; use function array_merge; use function array_search; use function array_udiff; use function array_uintersect; use function current; use function end; use function in_array; use function is_int; use function reset; use function sprintf; use function unserialize; use function usort; /** * This class provides a basic implementation of `CollectionInterface`, to * minimize the effort required to implement this interface * * @template T * @extends AbstractArray * @implements CollectionInterface */ abstract class AbstractCollection extends AbstractArray implements CollectionInterface { use TypeTrait; use ValueToStringTrait; use ValueExtractorTrait; /** * @inheritDoc */ public function add($element): bool { $this[] = $element; return true; } /** * @inheritDoc */ public function contains($element, bool $strict = true): bool { return in_array($element, $this->data, $strict); } /** * @inheritDoc */ public function offsetSet($offset, $value): void { if ($this->checkType($this->getType(), $value) === false) { throw new InvalidArgumentException( 'Value must be of type ' . $this->getType() . '; value is ' . $this->toolValueToString($value) ); } if ($offset === null) { $this->data[] = $value; } else { $this->data[$offset] = $value; } } /** * @inheritDoc */ public function remove($element): bool { if (($position = array_search($element, $this->data, true)) !== false) { unset($this->data[$position]); return true; } return false; } /** * @inheritDoc */ public function column(string $propertyOrMethod): array { $temp = []; foreach ($this->data as $item) { /** @var mixed $value */ $value = $this->extractValue($item, $propertyOrMethod); /** @psalm-suppress MixedAssignment */ $temp[] = $value; } return $temp; } /** * @inheritDoc */ public function first() { if ($this->isEmpty()) { throw new OutOfBoundsException('Can\'t determine first item. Collection is empty'); } reset($this->data); /** @var T $first */ $first = current($this->data); return $first; } /** * @inheritDoc */ public function last() { if ($this->isEmpty()) { throw new OutOfBoundsException('Can\'t determine last item. Collection is empty'); } /** @var T $item */ $item = end($this->data); reset($this->data); return $item; } public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface { if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) { throw new InvalidSortOrderException('Invalid sort order given: ' . $order); } $collection = clone $this; usort( $collection->data, /** * @param T $a * @param T $b */ function ($a, $b) use ($propertyOrMethod, $order): int { /** @var mixed $aValue */ $aValue = $this->extractValue($a, $propertyOrMethod); /** @var mixed $bValue */ $bValue = $this->extractValue($b, $propertyOrMethod); return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1); } ); return $collection; } public function filter(callable $callback): CollectionInterface { $collection = clone $this; $collection->data = array_merge([], array_filter($collection->data, $callback)); return $collection; } /** * {@inheritdoc} */ public function where(string $propertyOrMethod, $value): CollectionInterface { return $this->filter(function ($item) use ($propertyOrMethod, $value) { /** @var mixed $accessorValue */ $accessorValue = $this->extractValue($item, $propertyOrMethod); return $accessorValue === $value; }); } public function map(callable $callback): CollectionInterface { return new Collection('mixed', array_map($callback, $this->data)); } public function diff(CollectionInterface $other): CollectionInterface { $this->compareCollectionTypes($other); $diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator()); $diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator()); /** @var array $diff */ $diff = array_merge($diffAtoB, $diffBtoA); $collection = clone $this; $collection->data = $diff; return $collection; } public function intersect(CollectionInterface $other): CollectionInterface { $this->compareCollectionTypes($other); /** @var array $intersect */ $intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator()); $collection = clone $this; $collection->data = $intersect; return $collection; } public function merge(CollectionInterface ...$collections): CollectionInterface { $mergedCollection = clone $this; foreach ($collections as $index => $collection) { if (!$collection instanceof static) { throw new CollectionMismatchException( sprintf('Collection with index %d must be of type %s', $index, static::class) ); } // When using generics (Collection.php, Set.php, etc), // we also need to make sure that the internal types match each other if ($collection->getType() !== $this->getType()) { throw new CollectionMismatchException( sprintf('Collection items in collection with index %d must be of type %s', $index, $this->getType()) ); } foreach ($collection as $key => $value) { if (is_int($key)) { $mergedCollection[] = $value; } else { $mergedCollection[$key] = $value; } } } return $mergedCollection; } /** * @inheritDoc */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]); $this->data = $data; } /** * @param CollectionInterface $other */ private function compareCollectionTypes(CollectionInterface $other): void { if (!$other instanceof static) { throw new CollectionMismatchException('Collection must be of type ' . static::class); } // When using generics (Collection.php, Set.php, etc), // we also need to make sure that the internal types match each other if ($other->getType() !== $this->getType()) { throw new CollectionMismatchException('Collection items must be of type ' . $this->getType()); } } private function getComparator(): Closure { return /** * @param T $a * @param T $b */ function ($a, $b): int { // If the two values are object, we convert them to unique scalars. // If the collection contains mixed values (unlikely) where some are objects // and some are not, we leave them as they are. // The comparator should still work and the result of $a < $b should // be consistent but unpredictable since not documented. if (is_object($a) && is_object($b)) { $a = spl_object_id($a); $b = spl_object_id($b); } return $a === $b ? 0 : ($a < $b ? 1 : -1); }; } } PKZ966DegradedUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; /** * @deprecated DegradedUuid is no longer necessary to represent UUIDs on 32-bit * systems. Transition typehints to {@see UuidInterface}. * * @psalm-immutable */ class DegradedUuid extends Uuid { } PKZ|l7?55DeprecatedUuidMethodsTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\DateTimeException; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Throwable; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * This trait encapsulates deprecated methods for ramsey/uuid; this trait and * its methods will be removed in ramsey/uuid 5.0.0. * * @deprecated This trait and its methods will be removed in ramsey/uuid 5.0.0. * * @psalm-immutable */ trait DeprecatedUuidMethodsTrait { /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getClockSeqHiAndReserved(): string { return $this->numberConverter->fromHex($this->fields->getClockSeqHiAndReserved()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()}. */ public function getClockSeqHiAndReservedHex(): string { return $this->fields->getClockSeqHiAndReserved()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getClockSeqLow(): string { return $this->numberConverter->fromHex($this->fields->getClockSeqLow()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()}. */ public function getClockSeqLowHex(): string { return $this->fields->getClockSeqLow()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getClockSequence(): string { return $this->numberConverter->fromHex($this->fields->getClockSeq()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()}. */ public function getClockSequenceHex(): string { return $this->fields->getClockSeq()->toString(); } /** * @deprecated This method will be removed in 5.0.0. There is no alternative * recommendation, so plan accordingly. */ public function getNumberConverter(): NumberConverterInterface { return $this->numberConverter; } /** * @deprecated In ramsey/uuid version 5.0.0, this will be removed. * It is available at {@see UuidV1::getDateTime()}. * * @return DateTimeImmutable An immutable instance of DateTimeInterface * * @throws UnsupportedOperationException if UUID is not time-based * @throws DateTimeException if DateTime throws an exception/error */ public function getDateTime(): DateTimeInterface { if ($this->fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); try { return new DateTimeImmutable( '@' . $time->getSeconds()->toString() . '.' . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) ); } catch (Throwable $e) { throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); } } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. * * @return string[] */ public function getFieldsHex(): array { return [ 'time_low' => $this->fields->getTimeLow()->toString(), 'time_mid' => $this->fields->getTimeMid()->toString(), 'time_hi_and_version' => $this->fields->getTimeHiAndVersion()->toString(), 'clock_seq_hi_and_reserved' => $this->fields->getClockSeqHiAndReserved()->toString(), 'clock_seq_low' => $this->fields->getClockSeqLow()->toString(), 'node' => $this->fields->getNode()->toString(), ]; } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getLeastSignificantBits(): string { $leastSignificantHex = substr($this->getHex()->toString(), 16); return $this->numberConverter->fromHex($leastSignificantHex); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getLeastSignificantBitsHex(): string { return substr($this->getHex()->toString(), 16); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getMostSignificantBits(): string { $mostSignificantHex = substr($this->getHex()->toString(), 0, 16); return $this->numberConverter->fromHex($mostSignificantHex); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getMostSignificantBitsHex(): string { return substr($this->getHex()->toString(), 0, 16); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()} and use the * arbitrary-precision math library of your choice to convert it to a * string integer. */ public function getNode(): string { return $this->numberConverter->fromHex($this->fields->getNode()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()}. */ public function getNodeHex(): string { return $this->fields->getNode()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. */ public function getTimeHiAndVersion(): string { return $this->numberConverter->fromHex($this->fields->getTimeHiAndVersion()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()}. */ public function getTimeHiAndVersionHex(): string { return $this->fields->getTimeHiAndVersion()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()} and use the * arbitrary-precision math library of your choice to convert it to a * string integer. */ public function getTimeLow(): string { return $this->numberConverter->fromHex($this->fields->getTimeLow()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()}. */ public function getTimeLowHex(): string { return $this->fields->getTimeLow()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()} and use the * arbitrary-precision math library of your choice to convert it to a * string integer. */ public function getTimeMid(): string { return $this->numberConverter->fromHex($this->fields->getTimeMid()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()}. */ public function getTimeMidHex(): string { return $this->fields->getTimeMid()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()} and use * the arbitrary-precision math library of your choice to convert it to * a string integer. */ public function getTimestamp(): string { if ($this->fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return $this->numberConverter->fromHex($this->fields->getTimestamp()->toString()); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()}. */ public function getTimestampHex(): string { if ($this->fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return $this->fields->getTimestamp()->toString(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}. */ public function getVariant(): ?int { return $this->fields->getVariant(); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}. */ public function getVersion(): ?int { return $this->fields->getVersion(); } } PKZ݁BinaryUtils.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; /** * Provides binary math utilities */ class BinaryUtils { /** * Applies the RFC 4122 variant field to the 16-bit clock sequence * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant * * @param int $clockSeq The 16-bit clock sequence value before the RFC 4122 * variant is applied * * @return int The 16-bit clock sequence multiplexed with the UUID variant * * @psalm-pure */ public static function applyVariant(int $clockSeq): int { $clockSeq = $clockSeq & 0x3fff; $clockSeq |= 0x8000; return $clockSeq; } /** * Applies the RFC 4122 version number to the 16-bit `time_hi_and_version` field * * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version * * @param int $timeHi The value of the 16-bit `time_hi_and_version` field * before the RFC 4122 version is applied * @param int $version The RFC 4122 version to apply to the `time_hi` field * * @return int The 16-bit time_hi field of the timestamp multiplexed with * the UUID version number * * @psalm-pure */ public static function applyVersion(int $timeHi, int $version): int { $timeHi = $timeHi & 0x0fff; $timeHi |= $version << 12; return $timeHi; } } PKZC Validator/ValidatorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Validator; /** * A validator validates a string as a proper UUID * * @psalm-immutable */ interface ValidatorInterface { /** * Returns the regular expression pattern used by this validator * * @return string The regular expression pattern this validator uses * * @psalm-return non-empty-string */ public function getPattern(): string; /** * Returns true if the provided string represents a UUID * * @param string $uuid The string to validate as a UUID * * @return bool True if the string is a valid UUID, false otherwise */ public function validate(string $uuid): bool; } PKZm4t:jjValidator/GenericValidator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Validator; use Ramsey\Uuid\Uuid; use function preg_match; use function str_replace; /** * GenericValidator validates strings as UUIDs of any variant * * @psalm-immutable */ final class GenericValidator implements ValidatorInterface { /** * Regular expression pattern for matching a UUID of any variant. */ private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\z'; /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function getPattern(): string { return self::VALID_PATTERN; } public function validate(string $uuid): bool { $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); } } PKZfEELazy/LazyUuidFromString.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Lazy; use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Fields\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Rfc4122\UuidV6; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\UuidFactory; use Ramsey\Uuid\UuidInterface; use ValueError; use function assert; use function bin2hex; use function hex2bin; use function sprintf; use function str_replace; use function substr; /** * Lazy version of a UUID: its format has not been determined yet, so it is mostly only usable for string/bytes * conversion. This object optimizes instantiation, serialization and string conversion time, at the cost of * increased overhead for more advanced UUID operations. * * @internal this type is used internally for performance reasons, and is not supposed to be directly referenced * in consumer libraries. * * @psalm-immutable * * Note: the {@see FieldsInterface} does not declare methods that deprecated API * relies upon: the API has been ported from the {@see \Ramsey\Uuid\Uuid} definition, * and is deprecated anyway. * Note: the deprecated API from {@see \Ramsey\Uuid\Uuid} is in use here (on purpose): it will be removed * once the deprecated API is gone from this class too. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod */ final class LazyUuidFromString implements UuidInterface { public const VALID_REGEX = '/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/ms'; private ?UuidInterface $unwrapped = null; /** * @psalm-param non-empty-string $uuid */ public function __construct(private string $uuid) { } /** @psalm-pure */ public static function fromBytes(string $bytes): self { $base16Uuid = bin2hex($bytes); return new self( substr($base16Uuid, 0, 8) . '-' . substr($base16Uuid, 8, 4) . '-' . substr($base16Uuid, 12, 4) . '-' . substr($base16Uuid, 16, 4) . '-' . substr($base16Uuid, 20, 12) ); } public function serialize(): string { return $this->uuid; } /** * @return array{string: string} * * @psalm-return array{string: non-empty-string} */ public function __serialize(): array { return ['string' => $this->uuid]; } /** * {@inheritDoc} * * @param string $data * * @psalm-param non-empty-string $data */ public function unserialize(string $data): void { $this->uuid = $data; } /** * @param array{string?: string} $data * * @psalm-param array{string?: non-empty-string} $data * @psalm-suppress UnusedMethodCall */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } /** @psalm-suppress DeprecatedMethod */ public function getNumberConverter(): NumberConverterInterface { return ($this->unwrapped ?? $this->unwrap()) ->getNumberConverter(); } /** * {@inheritDoc} * * @psalm-suppress DeprecatedMethod */ public function getFieldsHex(): array { return ($this->unwrapped ?? $this->unwrap()) ->getFieldsHex(); } /** @psalm-suppress DeprecatedMethod */ public function getClockSeqHiAndReservedHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getClockSeqHiAndReservedHex(); } /** @psalm-suppress DeprecatedMethod */ public function getClockSeqLowHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getClockSeqLowHex(); } /** @psalm-suppress DeprecatedMethod */ public function getClockSequenceHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getClockSequenceHex(); } /** @psalm-suppress DeprecatedMethod */ public function getDateTime(): DateTimeInterface { return ($this->unwrapped ?? $this->unwrap()) ->getDateTime(); } /** @psalm-suppress DeprecatedMethod */ public function getLeastSignificantBitsHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getLeastSignificantBitsHex(); } /** @psalm-suppress DeprecatedMethod */ public function getMostSignificantBitsHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getMostSignificantBitsHex(); } /** @psalm-suppress DeprecatedMethod */ public function getNodeHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getNodeHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimeHiAndVersionHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimeHiAndVersionHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimeLowHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimeLowHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimeMidHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimeMidHex(); } /** @psalm-suppress DeprecatedMethod */ public function getTimestampHex(): string { return ($this->unwrapped ?? $this->unwrap()) ->getTimestampHex(); } /** @psalm-suppress DeprecatedMethod */ public function getUrn(): string { return ($this->unwrapped ?? $this->unwrap()) ->getUrn(); } /** @psalm-suppress DeprecatedMethod */ public function getVariant(): ?int { return ($this->unwrapped ?? $this->unwrap()) ->getVariant(); } /** @psalm-suppress DeprecatedMethod */ public function getVersion(): ?int { return ($this->unwrapped ?? $this->unwrap()) ->getVersion(); } public function compareTo(UuidInterface $other): int { return ($this->unwrapped ?? $this->unwrap()) ->compareTo($other); } public function equals(?object $other): bool { if (! $other instanceof UuidInterface) { return false; } return $this->uuid === $other->toString(); } /** * {@inheritDoc} * * @psalm-suppress MoreSpecificReturnType * @psalm-suppress LessSpecificReturnStatement we know that {@see self::$uuid} is a non-empty string, so * we know that {@see hex2bin} will retrieve a non-empty string too. */ public function getBytes(): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return (string) hex2bin(str_replace('-', '', $this->uuid)); } public function getFields(): FieldsInterface { return ($this->unwrapped ?? $this->unwrap()) ->getFields(); } public function getHex(): Hexadecimal { return ($this->unwrapped ?? $this->unwrap()) ->getHex(); } public function getInteger(): IntegerObject { return ($this->unwrapped ?? $this->unwrap()) ->getInteger(); } public function toString(): string { return $this->uuid; } public function __toString(): string { return $this->uuid; } public function jsonSerialize(): string { return $this->uuid; } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getClockSeqHiAndReserved(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getClockSeqHiAndReserved() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getClockSeqLow(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getClockSeqLow() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getClockSequence(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getClockSeq() ->toString() ); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getLeastSignificantBits(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex(substr($instance->getHex()->toString(), 16)); } /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getMostSignificantBits(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex(substr($instance->getHex()->toString(), 0, 16)); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getNode()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getNode(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getNode() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimeHiAndVersion(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getTimeHiAndVersion() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimeLow(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getTimeLow() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimeMid(): string { $instance = ($this->unwrapped ?? $this->unwrap()); return $instance->getNumberConverter() ->fromHex( $instance->getFields() ->getTimeMid() ->toString() ); } /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface} * instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()} * and use the arbitrary-precision math library of your choice to * convert it to a string integer. * * @psalm-suppress UndefinedInterfaceMethod * @psalm-suppress DeprecatedMethod * @psalm-suppress MixedArgument * @psalm-suppress MixedMethodCall */ public function getTimestamp(): string { $instance = ($this->unwrapped ?? $this->unwrap()); $fields = $instance->getFields(); if ($fields->getVersion() !== 1) { throw new UnsupportedOperationException('Not a time-based UUID'); } return $instance->getNumberConverter() ->fromHex($fields->getTimestamp()->toString()); } public function toUuidV1(): UuidV1 { $instance = ($this->unwrapped ?? $this->unwrap()); if ($instance instanceof UuidV1) { return $instance; } assert($instance instanceof UuidV6); return $instance->toUuidV1(); } public function toUuidV6(): UuidV6 { $instance = ($this->unwrapped ?? $this->unwrap()); assert($instance instanceof UuidV6); return $instance; } /** * @psalm-suppress ImpureMethodCall the retrieval of the factory is a clear violation of purity here: this is a * known pitfall of the design of this library, where a value object contains * a mutable reference to a factory. We use a fixed factory here, so the violation * will not have real-world effects, as this object is only instantiated with the * default factory settings/features. * @psalm-suppress InaccessibleProperty property {@see $unwrapped} is used as a cache: we don't expose it to the * outside world, so we should be fine here. */ private function unwrap(): UuidInterface { return $this->unwrapped = (new UuidFactory()) ->fromString($this->uuid); } } PKZ_~vRfc4122/UuidV2.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Uuid; use function hexdec; /** * DCE Security version, or version 2, UUIDs include local domain identifier, * local ID for the specified domain, and node values that are combined into a * 128-bit unsigned integer * * It is important to note that a version 2 UUID suffers from some loss of * fidelity of the timestamp, due to replacing the time_low field with the * local identifier. When constructing the timestamp value for date * purposes, we replace the local identifier bits with zeros. As a result, * the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7 * minutes, 9 seconds, and 496730 microseconds). * * Astute observers might note this value directly corresponds to 2^32 - 1, * or 0xffffffff. The local identifier is 32-bits, and we have set each of * these bits to 0, so the maximum range of timestamp drift is 0x00000000 * to 0xffffffff (counted in 100-nanosecond intervals). * * @link https://publications.opengroup.org/c311 DCE 1.1: Authentication and Security Services * @link https://publications.opengroup.org/c706 DCE 1.1: Remote Procedure Call * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 DCE 1.1: Auth & Sec, §5.2.1.1 * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1: Auth & Sec, §11.5.1.1 * @link https://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm DCE 1.1: RPC, Appendix A * @link https://github.com/google/uuid Go package for UUIDs (includes DCE implementation) * * @psalm-immutable */ final class UuidV2 extends Uuid implements UuidInterface { use TimeTrait; /** * Creates a version 2 (DCE Security) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_DCE_SECURITY) { throw new InvalidArgumentException( 'Fields used to create a UuidV2 must represent a ' . 'version 2 (DCE Security) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } /** * Returns the local domain used to create this version 2 UUID */ public function getLocalDomain(): int { /** @var Rfc4122FieldsInterface $fields */ $fields = $this->getFields(); return (int) hexdec($fields->getClockSeqLow()->toString()); } /** * Returns the string name of the local domain */ public function getLocalDomainName(): string { return Uuid::DCE_DOMAIN_NAMES[$this->getLocalDomain()]; } /** * Returns the local identifier for the domain used to create this version 2 UUID */ public function getLocalIdentifier(): IntegerObject { /** @var Rfc4122FieldsInterface $fields */ $fields = $this->getFields(); return new IntegerObject( $this->numberConverter->fromHex($fields->getTimeLow()->toString()) ); } } PKZ>>Rfc4122/MaxUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Uuid; /** * The max UUID is special form of UUID that is specified to have all 128 bits * set to one * * @psalm-immutable */ final class MaxUuid extends Uuid implements UuidInterface { } PKZ  Rfc4122/UuidV6.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Nonstandard\UuidV6 as NonstandardUuidV6; /** * Reordered time, or version 6, UUIDs include timestamp, clock sequence, and * node values that are combined into a 128-bit unsigned integer * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.6 UUID Version 6 * * @psalm-immutable */ final class UuidV6 extends NonstandardUuidV6 implements UuidInterface { } PKZ;;Rfc4122/UuidV7.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Unix Epoch time, or version 7, UUIDs include a timestamp in milliseconds * since the Unix Epoch, along with random bytes * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.7 UUID Version 7 * * @psalm-immutable */ final class UuidV7 extends Uuid implements UuidInterface { use TimeTrait; /** * Creates a version 7 (Unix Epoch time) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_UNIX_TIME) { throw new InvalidArgumentException( 'Fields used to create a UuidV7 must represent a ' . 'version 7 (Unix Epoch time) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZbnRfc4122/MaxTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; /** * Provides common functionality for max UUIDs * * The max UUID is special form of UUID that is specified to have all 128 bits * set to one. It is the inverse of the nil UUID. * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.10 Max UUID * * @psalm-immutable */ trait MaxTrait { /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns true if the byte string represents a max UUID */ public function isMax(): bool { return $this->getBytes() === "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; } } PKZf[Rfc4122/UuidV1.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Gregorian time, or version 1, UUIDs include timestamp, clock sequence, and node * values that are combined into a 128-bit unsigned integer * * @psalm-immutable */ final class UuidV1 extends Uuid implements UuidInterface { use TimeTrait; /** * Creates a version 1 (Gregorian time) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_TIME) { throw new InvalidArgumentException( 'Fields used to create a UuidV1 must represent a ' . 'version 1 (time-based) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZ}uRfc4122/UuidV3.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Version 3 UUIDs are named-based, using combination of a namespace and name * that are hashed into a 128-bit unsigned integer using MD5 * * @psalm-immutable */ final class UuidV3 extends Uuid implements UuidInterface { /** * Creates a version 3 (name-based, MD5-hashed) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_HASH_MD5) { throw new InvalidArgumentException( 'Fields used to create a UuidV3 must represent a ' . 'version 3 (name-based, MD5-hashed) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZf>Rfc4122/UuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\Time\UnixTimeConverter; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Throwable; /** * UuidBuilder builds instances of RFC 4122 UUIDs * * @psalm-immutable */ class UuidBuilder implements UuidBuilderInterface { private TimeConverterInterface $unixTimeConverter; /** * Constructs the DefaultUuidBuilder * * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the Uuid * @param TimeConverterInterface $timeConverter The time converter to use * for converting Gregorian time extracted from version 1, 2, and 6 * UUIDs to Unix timestamps * @param TimeConverterInterface|null $unixTimeConverter The time converter * to use for converter Unix Epoch time extracted from version 7 UUIDs * to Unix timestamps */ public function __construct( private NumberConverterInterface $numberConverter, private TimeConverterInterface $timeConverter, ?TimeConverterInterface $unixTimeConverter = null ) { $this->unixTimeConverter = $unixTimeConverter ?? new UnixTimeConverter(new BrickMathCalculator()); } /** * Builds and returns a Uuid * * @param CodecInterface $codec The codec to use for building this Uuid instance * @param string $bytes The byte string from which to construct a UUID * * @return Rfc4122UuidInterface UuidBuilder returns instances of Rfc4122UuidInterface * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { /** @var Fields $fields */ $fields = $this->buildFields($bytes); if ($fields->isNil()) { return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter); } if ($fields->isMax()) { return new MaxUuid($fields, $this->numberConverter, $codec, $this->timeConverter); } switch ($fields->getVersion()) { case Uuid::UUID_TYPE_TIME: return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter); case Uuid::UUID_TYPE_DCE_SECURITY: return new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter); case Uuid::UUID_TYPE_HASH_MD5: return new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter); case Uuid::UUID_TYPE_RANDOM: return new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter); case Uuid::UUID_TYPE_HASH_SHA1: return new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter); case Uuid::UUID_TYPE_REORDERED_TIME: return new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter); case Uuid::UUID_TYPE_UNIX_TIME: return new UuidV7($fields, $this->numberConverter, $codec, $this->unixTimeConverter); case Uuid::UUID_TYPE_CUSTOM: return new UuidV8($fields, $this->numberConverter, $codec, $this->timeConverter); } throw new UnsupportedOperationException( 'The UUID version in the given fields is not supported ' . 'by this UUID builder' ); } catch (Throwable $e) { throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Proxy method to allow injecting a mock, for testing */ protected function buildFields(string $bytes): FieldsInterface { return new Fields($bytes); } } PKZzRfc4122/UuidV5.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Version 5 UUIDs are named-based, using combination of a namespace and name * that are hashed into a 128-bit unsigned integer using SHA1 * * @psalm-immutable */ final class UuidV5 extends Uuid implements UuidInterface { /** * Creates a version 5 (name-based, SHA1-hashed) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_HASH_SHA1) { throw new InvalidArgumentException( 'Fields used to create a UuidV5 must represent a ' . 'version 5 (named-based, SHA1-hashed) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZ/Rfc4122/TimeTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Exception\DateTimeException; use Throwable; use function str_pad; use const STR_PAD_LEFT; /** * Provides common functionality for getting the time from a time-based UUID * * @psalm-immutable */ trait TimeTrait { /** * Returns a DateTimeInterface object representing the timestamp associated * with the UUID * * @return DateTimeImmutable A PHP DateTimeImmutable instance representing * the timestamp of a time-based UUID */ public function getDateTime(): DateTimeInterface { $time = $this->timeConverter->convertTime($this->fields->getTimestamp()); try { return new DateTimeImmutable( '@' . $time->getSeconds()->toString() . '.' . str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT) ); } catch (Throwable $e) { throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e); } } } PKZ*Rfc4122/Validator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Validator\ValidatorInterface; use function preg_match; use function str_replace; /** * Rfc4122\Validator validates strings as UUIDs of the RFC 4122 variant * * @psalm-immutable */ final class Validator implements ValidatorInterface { private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-' . '[1-8][0-9A-Fa-f]{3}-[ABab89][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\z'; /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function getPattern(): string { return self::VALID_PATTERN; } public function validate(string $uuid): bool { $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); $uuid = strtolower($uuid); return $uuid === Uuid::NIL || $uuid === Uuid::MAX || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); } } PKZ Rfc4122/VersionTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Uuid; /** * Provides common functionality for handling the version, as defined by RFC 4122 * * @psalm-immutable */ trait VersionTrait { /** * Returns the version */ abstract public function getVersion(): ?int; /** * Returns true if these fields represent a max UUID */ abstract public function isMax(): bool; /** * Returns true if these fields represent a nil UUID */ abstract public function isNil(): bool; /** * Returns true if the version matches one of those defined by RFC 4122 * * @return bool True if the UUID version is valid, false otherwise */ private function isCorrectVersion(): bool { if ($this->isNil() || $this->isMax()) { return true; } return match ($this->getVersion()) { Uuid::UUID_TYPE_TIME, Uuid::UUID_TYPE_DCE_SECURITY, Uuid::UUID_TYPE_HASH_MD5, Uuid::UUID_TYPE_RANDOM, Uuid::UUID_TYPE_HASH_SHA1, Uuid::UUID_TYPE_REORDERED_TIME, Uuid::UUID_TYPE_UNIX_TIME, Uuid::UUID_TYPE_CUSTOM => true, default => false, }; } } PKZ'Rfc4122/NilTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; /** * Provides common functionality for nil UUIDs * * The nil UUID is special form of UUID that is specified to have all 128 bits * set to zero. * * @link https://tools.ietf.org/html/rfc4122#section-4.1.7 RFC 4122, § 4.1.7: Nil UUID * * @psalm-immutable */ trait NilTrait { /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns true if the byte string represents a nil UUID */ public function isNil(): bool { return $this->getBytes() === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; } } PKZ.% % Rfc4122/UuidV8.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Version 8, Custom UUIDs provide an RFC 4122 compatible format for * experimental or vendor-specific uses * * The only requirement for version 8 UUIDs is that the version and variant bits * must be set. Otherwise, implementations are free to set the other bits * according to their needs. As a result, the uniqueness of version 8 UUIDs is * implementation-specific and should not be assumed. * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.8 UUID Version 8 * * @psalm-immutable */ final class UuidV8 extends Uuid implements UuidInterface { /** * Creates a version 8 (custom) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_CUSTOM) { throw new InvalidArgumentException( 'Fields used to create a UuidV8 must represent a ' . 'version 8 (custom) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZZ##Rfc4122/FieldsInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Fields\FieldsInterface as BaseFieldsInterface; use Ramsey\Uuid\Type\Hexadecimal; /** * RFC 4122 defines fields for a specific variant of UUID * * The fields of an RFC 4122 variant UUID are: * * * **time_low**: The low field of the timestamp, an unsigned 32-bit integer * * **time_mid**: The middle field of the timestamp, an unsigned 16-bit integer * * **time_hi_and_version**: The high field of the timestamp multiplexed with * the version number, an unsigned 16-bit integer * * **clock_seq_hi_and_reserved**: The high field of the clock sequence * multiplexed with the variant, an unsigned 8-bit integer * * **clock_seq_low**: The low field of the clock sequence, an unsigned * 8-bit integer * * **node**: The spatially unique node identifier, an unsigned 48-bit * integer * * @link http://tools.ietf.org/html/rfc4122#section-4.1 RFC 4122, § 4.1: Format * * @psalm-immutable */ interface FieldsInterface extends BaseFieldsInterface { /** * Returns the full 16-bit clock sequence, with the variant bits (two most * significant bits) masked out */ public function getClockSeq(): Hexadecimal; /** * Returns the high field of the clock sequence multiplexed with the variant */ public function getClockSeqHiAndReserved(): Hexadecimal; /** * Returns the low field of the clock sequence */ public function getClockSeqLow(): Hexadecimal; /** * Returns the node field */ public function getNode(): Hexadecimal; /** * Returns the high field of the timestamp multiplexed with the version */ public function getTimeHiAndVersion(): Hexadecimal; /** * Returns the low field of the timestamp */ public function getTimeLow(): Hexadecimal; /** * Returns the middle field of the timestamp */ public function getTimeMid(): Hexadecimal; /** * Returns the full 60-bit timestamp, without the version */ public function getTimestamp(): Hexadecimal; /** * Returns the variant * * The variant number describes the layout of the UUID. The variant * number has the following meaning: * * - 0 - Reserved for NCS backward compatibility * - 2 - The RFC 4122 variant * - 6 - Reserved, Microsoft Corporation backward compatibility * - 7 - Reserved for future definition * * For RFC 4122 variant UUIDs, this value should always be the integer `2`. * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public function getVariant(): int; /** * Returns the version * * The version number describes how the UUID was generated and has the * following meaning: * * 1. Gregorian time UUID * 2. DCE security UUID * 3. Name-based UUID hashed with MD5 * 4. Randomly generated UUID * 5. Name-based UUID hashed with SHA-1 * 6. Reordered time UUID * 7. Unix Epoch time UUID * * This returns `null` if the UUID is not an RFC 4122 variant, since version * is only meaningful for this variant. * * @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public function getVersion(): ?int; /** * Returns true if these fields represent a nil UUID * * The nil UUID is special form of UUID that is specified to have all 128 * bits set to zero. */ public function isNil(): bool; } PKZJJRfc4122/Fields.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Uuid; use function bin2hex; use function dechex; use function hexdec; use function sprintf; use function str_pad; use function strlen; use function substr; use function unpack; use const STR_PAD_LEFT; /** * RFC 4122 variant UUIDs are comprised of a set of named fields * * Internally, this class represents the fields together as a 16-byte binary * string. * * @psalm-immutable */ final class Fields implements FieldsInterface { use MaxTrait; use NilTrait; use SerializableFieldsTrait; use VariantTrait; use VersionTrait; /** * @param string $bytes A 16-byte binary string representation of a UUID * * @throws InvalidArgumentException if the byte string is not exactly 16 bytes * @throws InvalidArgumentException if the byte string does not represent an RFC 4122 UUID * @throws InvalidArgumentException if the byte string does not contain a valid version */ public function __construct(private string $bytes) { if (strlen($this->bytes) !== 16) { throw new InvalidArgumentException( 'The byte string must be 16 bytes long; ' . 'received ' . strlen($this->bytes) . ' bytes' ); } if (!$this->isCorrectVariant()) { throw new InvalidArgumentException( 'The byte string received does not conform to the RFC 4122 variant' ); } if (!$this->isCorrectVersion()) { throw new InvalidArgumentException( 'The byte string received does not contain a valid RFC 4122 version' ); } } public function getBytes(): string { return $this->bytes; } public function getClockSeq(): Hexadecimal { if ($this->isMax()) { $clockSeq = 0xffff; } elseif ($this->isNil()) { $clockSeq = 0x0000; } else { $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; } return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } public function getClockSeqHiAndReserved(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); } public function getClockSeqLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); } public function getNode(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 10))); } public function getTimeHiAndVersion(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2))); } public function getTimeLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4))); } public function getTimeMid(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2))); } /** * Returns the full 60-bit timestamp, without the version * * For version 2 UUIDs, the time_low field is the local identifier and * should not be returned as part of the time. For this reason, we set the * bottom 32 bits of the timestamp to 0's. As a result, there is some loss * of fidelity of the timestamp, for version 2 UUIDs. The timestamp can be * off by a range of 0 to 429.4967295 seconds (or 7 minutes, 9 seconds, and * 496730 microseconds). * * For version 6 UUIDs, the timestamp order is reversed from the typical RFC * 4122 order (the time bits are in the correct bit order, so that it is * monotonically increasing). In returning the timestamp value, we put the * bits in the order: time_low + time_mid + time_hi. */ public function getTimestamp(): Hexadecimal { $timestamp = match ($this->getVersion()) { Uuid::UUID_TYPE_DCE_SECURITY => sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), '' ), Uuid::UUID_TYPE_REORDERED_TIME => sprintf( '%08s%04s%03x', $this->getTimeLow()->toString(), $this->getTimeMid()->toString(), hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff ), // The Unix timestamp in version 7 UUIDs is a 48-bit number, // but for consistency, we will return a 60-bit number, padded // to the left with zeros. Uuid::UUID_TYPE_UNIX_TIME => sprintf( '%011s%04s', $this->getTimeLow()->toString(), $this->getTimeMid()->toString(), ), default => sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), $this->getTimeLow()->toString() ), }; return new Hexadecimal($timestamp); } public function getVersion(): ?int { if ($this->isNil() || $this->isMax()) { return null; } /** @var int[] $parts */ $parts = unpack('n*', $this->bytes); return $parts[4] >> 12; } private function isCorrectVariant(): bool { if ($this->isNil() || $this->isMax()) { return true; } return $this->getVariant() === Uuid::RFC_4122; } } PKZRfc4122/UuidV4.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; /** * Random, or version 4, UUIDs are randomly or pseudo-randomly generated 128-bit * integers * * @psalm-immutable */ final class UuidV4 extends Uuid implements UuidInterface { /** * Creates a version 4 (random) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_RANDOM) { throw new InvalidArgumentException( 'Fields used to create a UuidV4 must represent a ' . 'version 4 (random) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZcM??Rfc4122/NilUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Uuid; /** * The nil UUID is special form of UUID that is specified to have all 128 bits * set to zero * * @psalm-immutable */ final class NilUuid extends Uuid implements UuidInterface { } PKZw=Rfc4122/UuidInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\UuidInterface as BaseUuidInterface; /** * Also known as a Leach-Salz variant UUID, an RFC 4122 variant UUID is a * universally unique identifier defined by RFC 4122 * * @link https://tools.ietf.org/html/rfc4122 RFC 4122 * * @psalm-immutable */ interface UuidInterface extends BaseUuidInterface { } PKZ> x x Rfc4122/VariantTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Rfc4122; use Ramsey\Uuid\Exception\InvalidBytesException; use Ramsey\Uuid\Uuid; use function decbin; use function str_pad; use function str_starts_with; use function strlen; use function substr; use function unpack; use const STR_PAD_LEFT; /** * Provides common functionality for handling the variant, as defined by RFC 4122 * * @psalm-immutable */ trait VariantTrait { /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns the variant identifier, according to RFC 4122, for the given bytes * * The following values may be returned: * * - `0` -- Reserved, NCS backward compatibility. * - `2` -- The variant specified in RFC 4122. * - `6` -- Reserved, Microsoft Corporation backward compatibility. * - `7` -- Reserved for future definition. * * @link https://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant * * @return int The variant identifier, according to RFC 4122 */ public function getVariant(): int { if (strlen($this->getBytes()) !== 16) { throw new InvalidBytesException('Invalid number of bytes'); } if ($this->isMax() || $this->isNil()) { // RFC 4122 defines these special types of UUID, so we will consider // them as belonging to the RFC 4122 variant. return Uuid::RFC_4122; } /** @var int[] $parts */ $parts = unpack('n*', $this->getBytes()); // $parts[5] is a 16-bit, unsigned integer containing the variant bits // of the UUID. We convert this integer into a string containing a // binary representation, padded to 16 characters. We analyze the first // three characters (three most-significant bits) to determine the // variant. $binary = str_pad( decbin($parts[5]), 16, '0', STR_PAD_LEFT ); $msb = substr($binary, 0, 3); if ($msb === '111') { return Uuid::RESERVED_FUTURE; } elseif ($msb === '110') { return Uuid::RESERVED_MICROSOFT; } elseif (str_starts_with($msb, '10')) { return Uuid::RFC_4122; } return Uuid::RESERVED_NCS; } } PKZ00FeatureSet.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use Ramsey\Uuid\Builder\FallbackBuilder; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Codec\GuidStringCodec; use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Converter\Time\PhpTimeConverter; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Generator\DceSecurityGenerator; use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; use Ramsey\Uuid\Generator\NameGeneratorFactory; use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\PeclUuidNameGenerator; use Ramsey\Uuid\Generator\PeclUuidRandomGenerator; use Ramsey\Uuid\Generator\PeclUuidTimeGenerator; use Ramsey\Uuid\Generator\RandomGeneratorFactory; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorFactory; use Ramsey\Uuid\Generator\TimeGeneratorInterface; use Ramsey\Uuid\Generator\UnixTimeGenerator; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider; use Ramsey\Uuid\Provider\DceSecurityProviderInterface; use Ramsey\Uuid\Provider\Node\FallbackNodeProvider; use Ramsey\Uuid\Provider\Node\RandomNodeProvider; use Ramsey\Uuid\Provider\Node\SystemNodeProvider; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\Time\SystemTimeProvider; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Ramsey\Uuid\Validator\GenericValidator; use Ramsey\Uuid\Validator\ValidatorInterface; use const PHP_INT_SIZE; /** * FeatureSet detects and exposes available features in the current environment * * A feature set is used by UuidFactory to determine the available features and * capabilities of the environment. */ class FeatureSet { private ?TimeProviderInterface $timeProvider = null; private CalculatorInterface $calculator; private CodecInterface $codec; private DceSecurityGeneratorInterface $dceSecurityGenerator; private NameGeneratorInterface $nameGenerator; private NodeProviderInterface $nodeProvider; private NumberConverterInterface $numberConverter; private RandomGeneratorInterface $randomGenerator; private TimeConverterInterface $timeConverter; private TimeGeneratorInterface $timeGenerator; private TimeGeneratorInterface $unixTimeGenerator; private UuidBuilderInterface $builder; private ValidatorInterface $validator; /** * @param bool $useGuids True build UUIDs using the GuidStringCodec * @param bool $force32Bit True to force the use of 32-bit functionality * (primarily for testing purposes) * @param bool $forceNoBigNumber (obsolete) * @param bool $ignoreSystemNode True to disable attempts to check for the * system node ID (primarily for testing purposes) * @param bool $enablePecl True to enable the use of the PeclUuidTimeGenerator * to generate version 1 UUIDs */ public function __construct( bool $useGuids = false, private bool $force32Bit = false, bool $forceNoBigNumber = false, private bool $ignoreSystemNode = false, private bool $enablePecl = false ) { $this->randomGenerator = $this->buildRandomGenerator(); $this->setCalculator(new BrickMathCalculator()); $this->builder = $this->buildUuidBuilder($useGuids); $this->codec = $this->buildCodec($useGuids); $this->nodeProvider = $this->buildNodeProvider(); $this->nameGenerator = $this->buildNameGenerator(); $this->setTimeProvider(new SystemTimeProvider()); $this->setDceSecurityProvider(new SystemDceSecurityProvider()); $this->validator = new GenericValidator(); assert($this->timeProvider !== null); $this->unixTimeGenerator = $this->buildUnixTimeGenerator(); } /** * Returns the builder configured for this environment */ public function getBuilder(): UuidBuilderInterface { return $this->builder; } /** * Returns the calculator configured for this environment */ public function getCalculator(): CalculatorInterface { return $this->calculator; } /** * Returns the codec configured for this environment */ public function getCodec(): CodecInterface { return $this->codec; } /** * Returns the DCE Security generator configured for this environment */ public function getDceSecurityGenerator(): DceSecurityGeneratorInterface { return $this->dceSecurityGenerator; } /** * Returns the name generator configured for this environment */ public function getNameGenerator(): NameGeneratorInterface { return $this->nameGenerator; } /** * Returns the node provider configured for this environment */ public function getNodeProvider(): NodeProviderInterface { return $this->nodeProvider; } /** * Returns the number converter configured for this environment */ public function getNumberConverter(): NumberConverterInterface { return $this->numberConverter; } /** * Returns the random generator configured for this environment */ public function getRandomGenerator(): RandomGeneratorInterface { return $this->randomGenerator; } /** * Returns the time converter configured for this environment */ public function getTimeConverter(): TimeConverterInterface { return $this->timeConverter; } /** * Returns the time generator configured for this environment */ public function getTimeGenerator(): TimeGeneratorInterface { return $this->timeGenerator; } /** * Returns the Unix Epoch time generator configured for this environment */ public function getUnixTimeGenerator(): TimeGeneratorInterface { return $this->unixTimeGenerator; } /** * Returns the validator configured for this environment */ public function getValidator(): ValidatorInterface { return $this->validator; } /** * Sets the calculator to use in this environment */ public function setCalculator(CalculatorInterface $calculator): void { $this->calculator = $calculator; $this->numberConverter = $this->buildNumberConverter($calculator); $this->timeConverter = $this->buildTimeConverter($calculator); /** @psalm-suppress RedundantPropertyInitializationCheck */ if (isset($this->timeProvider)) { $this->timeGenerator = $this->buildTimeGenerator($this->timeProvider); } } /** * Sets the DCE Security provider to use in this environment */ public function setDceSecurityProvider(DceSecurityProviderInterface $dceSecurityProvider): void { $this->dceSecurityGenerator = $this->buildDceSecurityGenerator($dceSecurityProvider); } /** * Sets the node provider to use in this environment */ public function setNodeProvider(NodeProviderInterface $nodeProvider): void { $this->nodeProvider = $nodeProvider; if (isset($this->timeProvider)) { $this->timeGenerator = $this->buildTimeGenerator($this->timeProvider); } } /** * Sets the time provider to use in this environment */ public function setTimeProvider(TimeProviderInterface $timeProvider): void { $this->timeProvider = $timeProvider; $this->timeGenerator = $this->buildTimeGenerator($timeProvider); } /** * Set the validator to use in this environment */ public function setValidator(ValidatorInterface $validator): void { $this->validator = $validator; } /** * Returns a codec configured for this environment * * @param bool $useGuids Whether to build UUIDs using the GuidStringCodec */ private function buildCodec(bool $useGuids = false): CodecInterface { if ($useGuids) { return new GuidStringCodec($this->builder); } return new StringCodec($this->builder); } /** * Returns a DCE Security generator configured for this environment */ private function buildDceSecurityGenerator( DceSecurityProviderInterface $dceSecurityProvider ): DceSecurityGeneratorInterface { return new DceSecurityGenerator( $this->numberConverter, $this->timeGenerator, $dceSecurityProvider ); } /** * Returns a node provider configured for this environment */ private function buildNodeProvider(): NodeProviderInterface { if ($this->ignoreSystemNode) { return new RandomNodeProvider(); } return new FallbackNodeProvider([ new SystemNodeProvider(), new RandomNodeProvider(), ]); } /** * Returns a number converter configured for this environment */ private function buildNumberConverter(CalculatorInterface $calculator): NumberConverterInterface { return new GenericNumberConverter($calculator); } /** * Returns a random generator configured for this environment */ private function buildRandomGenerator(): RandomGeneratorInterface { if ($this->enablePecl) { return new PeclUuidRandomGenerator(); } return (new RandomGeneratorFactory())->getGenerator(); } /** * Returns a time generator configured for this environment * * @param TimeProviderInterface $timeProvider The time provider to use with * the time generator */ private function buildTimeGenerator(TimeProviderInterface $timeProvider): TimeGeneratorInterface { if ($this->enablePecl) { return new PeclUuidTimeGenerator(); } return (new TimeGeneratorFactory( $this->nodeProvider, $this->timeConverter, $timeProvider ))->getGenerator(); } /** * Returns a Unix Epoch time generator configured for this environment */ private function buildUnixTimeGenerator(): TimeGeneratorInterface { return new UnixTimeGenerator($this->randomGenerator); } /** * Returns a name generator configured for this environment */ private function buildNameGenerator(): NameGeneratorInterface { if ($this->enablePecl) { return new PeclUuidNameGenerator(); } return (new NameGeneratorFactory())->getGenerator(); } /** * Returns a time converter configured for this environment */ private function buildTimeConverter(CalculatorInterface $calculator): TimeConverterInterface { $genericConverter = new GenericTimeConverter($calculator); if ($this->is64BitSystem()) { return new PhpTimeConverter($calculator, $genericConverter); } return $genericConverter; } /** * Returns a UUID builder configured for this environment * * @param bool $useGuids Whether to build UUIDs using the GuidStringCodec */ private function buildUuidBuilder(bool $useGuids = false): UuidBuilderInterface { if ($useGuids) { return new GuidBuilder($this->numberConverter, $this->timeConverter); } return new FallbackBuilder([ new Rfc4122UuidBuilder($this->numberConverter, $this->timeConverter), new NonstandardUuidBuilder($this->numberConverter, $this->timeConverter), ]); } /** * Returns true if the PHP build is 64-bit */ private function is64BitSystem(): bool { return PHP_INT_SIZE === 8 && !$this->force32Bit; } } PKZxx x Type/Time.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Type\Integer as IntegerObject; use ValueError; use function json_decode; use function json_encode; use function sprintf; /** * A value object representing a timestamp * * This class exists for type-safety purposes, to ensure that timestamps used * by ramsey/uuid are truly timestamp integers and not some other kind of string * or integer. * * @psalm-immutable */ final class Time implements TypeInterface { private IntegerObject $seconds; private IntegerObject $microseconds; public function __construct( float | int | string | IntegerObject $seconds, float | int | string | IntegerObject $microseconds = 0, ) { $this->seconds = new IntegerObject($seconds); $this->microseconds = new IntegerObject($microseconds); } public function getSeconds(): IntegerObject { return $this->seconds; } public function getMicroseconds(): IntegerObject { return $this->microseconds; } public function toString(): string { return $this->seconds->toString() . '.' . sprintf('%06s', $this->microseconds->toString()); } public function __toString(): string { return $this->toString(); } /** * @return string[] */ public function jsonSerialize(): array { return [ 'seconds' => $this->getSeconds()->toString(), 'microseconds' => $this->getMicroseconds()->toString(), ]; } public function serialize(): string { return (string) json_encode($this); } /** * @return array{seconds: string, microseconds: string} */ public function __serialize(): array { return [ 'seconds' => $this->getSeconds()->toString(), 'microseconds' => $this->getMicroseconds()->toString(), ]; } /** * Constructs the object from a serialized string representation * * @param string $data The serialized string representation of the object * * @psalm-suppress UnusedMethodCall */ public function unserialize(string $data): void { /** @var array{seconds?: int|float|string, microseconds?: int|float|string} $time */ $time = json_decode($data, true); if (!isset($time['seconds']) || !isset($time['microseconds'])) { throw new UnsupportedOperationException( 'Attempted to unserialize an invalid value' ); } $this->__construct($time['seconds'], $time['microseconds']); } /** * @param array{seconds?: string, microseconds?: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['seconds']) || !isset($data['microseconds'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->__construct($data['seconds'], $data['microseconds']); } } PKZ{{Type/NumberInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; /** * NumberInterface ensures consistency in numeric values returned by ramsey/uuid * * @psalm-immutable */ interface NumberInterface extends TypeInterface { /** * Returns true if this number is less than zero */ public function isNegative(): bool; } PKZTD,\ Type/Hexadecimal.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\InvalidArgumentException; use ValueError; use function preg_match; use function sprintf; use function substr; /** * A value object representing a hexadecimal number * * This class exists for type-safety purposes, to ensure that hexadecimal numbers * returned from ramsey/uuid methods as strings are truly hexadecimal and not some * other kind of string. * * @psalm-immutable */ final class Hexadecimal implements TypeInterface { private string $value; /** * @param self|string $value The hexadecimal value to store */ public function __construct(self | string $value) { $this->value = $value instanceof self ? (string) $value : $this->prepareValue($value); } public function toString(): string { return $this->value; } public function __toString(): string { return $this->toString(); } public function jsonSerialize(): string { return $this->toString(); } public function serialize(): string { return $this->toString(); } /** * @return array{string: string} */ public function __serialize(): array { return ['string' => $this->toString()]; } /** * Constructs the object from a serialized string representation * * @param string $data The serialized string representation of the object * * @psalm-suppress UnusedMethodCall */ public function unserialize(string $data): void { $this->__construct($data); } /** * @param array{string?: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } private function prepareValue(string $value): string { $value = strtolower($value); if (str_starts_with($value, '0x')) { $value = substr($value, 2); } if (!preg_match('/^[A-Fa-f0-9]+$/', $value)) { throw new InvalidArgumentException( 'Value must be a hexadecimal number' ); } return $value; } } PKZ4+ + Type/Decimal.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\InvalidArgumentException; use ValueError; use function is_numeric; use function sprintf; use function str_starts_with; /** * A value object representing a decimal * * This class exists for type-safety purposes, to ensure that decimals * returned from ramsey/uuid methods as strings are truly decimals and not some * other kind of string. * * To support values as true decimals and not as floats or doubles, we store the * decimals as strings. * * @psalm-immutable */ final class Decimal implements NumberInterface { private string $value; private bool $isNegative = false; public function __construct(float | int | string | self $value) { $value = (string) $value; if (!is_numeric($value)) { throw new InvalidArgumentException( 'Value must be a signed decimal or a string containing only ' . 'digits 0-9 and, optionally, a decimal point or sign (+ or -)' ); } // Remove the leading +-symbol. if (str_starts_with($value, '+')) { $value = substr($value, 1); } // For cases like `-0` or `-0.0000`, convert the value to `0`. if (abs((float) $value) === 0.0) { $value = '0'; } if (str_starts_with($value, '-')) { $this->isNegative = true; } $this->value = $value; } public function isNegative(): bool { return $this->isNegative; } public function toString(): string { return $this->value; } public function __toString(): string { return $this->toString(); } public function jsonSerialize(): string { return $this->toString(); } public function serialize(): string { return $this->toString(); } /** * @return array{string: string} */ public function __serialize(): array { return ['string' => $this->toString()]; } /** * Constructs the object from a serialized string representation * * @param string $data The serialized string representation of the object * * @psalm-suppress UnusedMethodCall */ public function unserialize(string $data): void { $this->__construct($data); } /** * @param array{string?: string} $data * * @psalm-suppress UnusedMethodCall */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } } PKZ{ޕType/TypeInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use JsonSerializable; use Serializable; /** * TypeInterface ensures consistency in typed values returned by ramsey/uuid * * @psalm-immutable */ interface TypeInterface extends JsonSerializable, Serializable { public function toString(): string; public function __toString(): string; } PKZ_~P##Type/Integer.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Type; use Ramsey\Uuid\Exception\InvalidArgumentException; use ValueError; use function assert; use function is_numeric; use function preg_match; use function sprintf; use function substr; /** * A value object representing an integer * * This class exists for type-safety purposes, to ensure that integers * returned from ramsey/uuid methods as strings are truly integers and not some * other kind of string. * * To support large integers beyond PHP_INT_MAX and PHP_INT_MIN on both 64-bit * and 32-bit systems, we store the integers as strings. * * @psalm-immutable */ final class Integer implements NumberInterface { /** * @psalm-var numeric-string */ private string $value; private bool $isNegative = false; public function __construct(float | int | string | self $value) { $this->value = $value instanceof self ? (string) $value : $this->prepareValue($value); } public function isNegative(): bool { return $this->isNegative; } /** * @psalm-return numeric-string */ public function toString(): string { return $this->value; } /** * @psalm-return numeric-string */ public function __toString(): string { return $this->toString(); } public function jsonSerialize(): string { return $this->toString(); } public function serialize(): string { return $this->toString(); } /** * @return array{string: string} */ public function __serialize(): array { return ['string' => $this->toString()]; } /** * Constructs the object from a serialized string representation * * @param string $data The serialized string representation of the object * * @psalm-suppress UnusedMethodCall */ public function unserialize(string $data): void { $this->__construct($data); } /** * @param array{string?: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['string'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['string']); } /** * @return numeric-string */ private function prepareValue(float | int | string $value): string { $value = (string) $value; $sign = '+'; // If the value contains a sign, remove it for digit pattern check. if (str_starts_with($value, '-') || str_starts_with($value, '+')) { $sign = substr($value, 0, 1); $value = substr($value, 1); } if (!preg_match('/^\d+$/', $value)) { throw new InvalidArgumentException( 'Value must be a signed integer or a string containing only ' . 'digits 0-9 and, optionally, a sign (+ or -)' ); } // Trim any leading zeros. $value = ltrim($value, '0'); // Set to zero if the string is empty after trimming zeros. if ($value === '') { $value = '0'; } // Add the negative sign back to the value. if ($sign === '-' && $value !== '0') { $value = $sign . $value; /** @psalm-suppress InaccessibleProperty */ $this->isNegative = true; } assert(is_numeric($value)); return $value; } } PKZ < < Nonstandard/UuidV6.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Rfc4122\TimeTrait; use Ramsey\Uuid\Rfc4122\UuidInterface; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Uuid; /** * Reordered time, or version 6, UUIDs include timestamp, clock sequence, and * node values that are combined into a 128-bit unsigned integer * * @deprecated Use {@see \Ramsey\Uuid\Rfc4122\UuidV6} instead. * * @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft * @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs * * @psalm-immutable */ class UuidV6 extends Uuid implements UuidInterface { use TimeTrait; /** * Creates a version 6 (reordered time) UUID * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { if ($fields->getVersion() !== Uuid::UUID_TYPE_REORDERED_TIME) { throw new InvalidArgumentException( 'Fields used to create a UuidV6 must represent a ' . 'version 6 (reordered time) UUID' ); } parent::__construct($fields, $numberConverter, $codec, $timeConverter); } /** * Converts this UUID into an instance of a version 1 UUID */ public function toUuidV1(): UuidV1 { $hex = $this->getHex()->toString(); $hex = substr($hex, 7, 5) . substr($hex, 13, 3) . substr($hex, 3, 4) . '1' . substr($hex, 0, 3) . substr($hex, 16); /** @var LazyUuidFromString $uuid */ $uuid = Uuid::fromBytes((string) hex2bin($hex)); return $uuid->toUuidV1(); } /** * Converts a version 1 UUID into an instance of a version 6 UUID */ public static function fromUuidV1(UuidV1 $uuidV1): \Ramsey\Uuid\Rfc4122\UuidV6 { $hex = $uuidV1->getHex()->toString(); $hex = substr($hex, 13, 3) . substr($hex, 8, 4) . substr($hex, 0, 5) . '6' . substr($hex, 5, 3) . substr($hex, 16); /** @var LazyUuidFromString $uuid */ $uuid = Uuid::fromBytes((string) hex2bin($hex)); return $uuid->toUuidV6(); } } PKZ Nonstandard/UuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\UuidInterface; use Throwable; /** * Nonstandard\UuidBuilder builds instances of Nonstandard\Uuid * * @psalm-immutable */ class UuidBuilder implements UuidBuilderInterface { /** * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the Nonstandard\Uuid * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( private NumberConverterInterface $numberConverter, private TimeConverterInterface $timeConverter ) { } /** * Builds and returns a Nonstandard\Uuid * * @param CodecInterface $codec The codec to use for building this instance * @param string $bytes The byte string from which to construct a UUID * * @return Uuid The Nonstandard\UuidBuilder returns an instance of * Nonstandard\Uuid * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { return new Uuid( $this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter ); } catch (Throwable $e) { throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Proxy method to allow injecting a mock, for testing */ protected function buildFields(string $bytes): Fields { return new Fields($bytes); } } PKZ>B #Nonstandard/Uuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Uuid as BaseUuid; /** * Nonstandard\Uuid is a UUID that doesn't conform to RFC 4122 * * @psalm-immutable */ final class Uuid extends BaseUuid { public function __construct( Fields $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZ!pD/ / Nonstandard/Fields.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Nonstandard; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\VariantTrait; use Ramsey\Uuid\Type\Hexadecimal; use function bin2hex; use function dechex; use function hexdec; use function sprintf; use function str_pad; use function strlen; use function substr; use const STR_PAD_LEFT; /** * Nonstandard UUID fields do not conform to the RFC 4122 standard * * Since some systems may create nonstandard UUIDs, this implements the * Rfc4122\FieldsInterface, so that functionality of a nonstandard UUID is not * degraded, in the event these UUIDs are expected to contain RFC 4122 fields. * * Internally, this class represents the fields together as a 16-byte binary * string. * * @psalm-immutable */ final class Fields implements FieldsInterface { use SerializableFieldsTrait; use VariantTrait; /** * @param string $bytes A 16-byte binary string representation of a UUID * * @throws InvalidArgumentException if the byte string is not exactly 16 bytes */ public function __construct(private string $bytes) { if (strlen($this->bytes) !== 16) { throw new InvalidArgumentException( 'The byte string must be 16 bytes long; ' . 'received ' . strlen($this->bytes) . ' bytes' ); } } public function getBytes(): string { return $this->bytes; } public function getClockSeq(): Hexadecimal { $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } public function getClockSeqHiAndReserved(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); } public function getClockSeqLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); } public function getNode(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 10))); } public function getTimeHiAndVersion(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2))); } public function getTimeLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4))); } public function getTimeMid(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2))); } public function getTimestamp(): Hexadecimal { return new Hexadecimal(sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), $this->getTimeLow()->toString() )); } public function getVersion(): ?int { return null; } public function isNil(): bool { return false; } public function isMax(): bool { return false; } } PKZ=DD!Exception/TimeSourceException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the source of time encountered an error */ class TimeSourceException extends PhpRuntimeException implements UuidExceptionInterface { } PKZTTException/DateTimeException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the PHP DateTime extension encountered an exception/error */ class DateTimeException extends PhpRuntimeException implements UuidExceptionInterface { } PKZ3tN#Exception/RandomSourceException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the source of random data encountered an error * * This exception is used mostly to indicate that random_bytes() or random_int() * threw an exception. However, it may be used for other sources of random data. */ class RandomSourceException extends PhpRuntimeException implements UuidExceptionInterface { } PKZo cc"Exception/DceSecurityException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate an exception occurred while dealing with DCE Security * (version 2) UUIDs */ class DceSecurityException extends PhpRuntimeException implements UuidExceptionInterface { } PKZveDD&Exception/BuilderNotFoundException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that no suitable builder could be found */ class BuilderNotFoundException extends PhpRuntimeException implements UuidExceptionInterface { } PKZN(Exception/InvalidUuidStringException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; /** * Thrown to indicate that the string received is not a valid UUID * * The InvalidArgumentException that this extends is the ramsey/uuid version * of this exception. It exists in the same namespace as this class. */ class InvalidUuidStringException extends InvalidArgumentException implements UuidExceptionInterface { } PKZR$Exception/UuidExceptionInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use Throwable; interface UuidExceptionInterface extends Throwable { } PKZouRR#Exception/InvalidBytesException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that the bytes being operated on are invalid in some way */ class InvalidBytesException extends PhpRuntimeException implements UuidExceptionInterface { } PKZYYException/NameException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that an error occurred while attempting to hash a * namespace and name */ class NameException extends PhpRuntimeException implements UuidExceptionInterface { } PKZ. SSException/NodeException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate that attempting to fetch or create a node ID encountered an error */ class NodeException extends PhpRuntimeException implements UuidExceptionInterface { } PKZH%_BB(Exception/UnableToBuildUuidException.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Exception; use RuntimeException as PhpRuntimeException; /** * Thrown to indicate a builder is unable to build a UUID */ class UnableToBuildUuidException extends PhpRuntimeException implements UuidExceptionInterface { } PKZ#<UuidFactoryInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Validator\ValidatorInterface; /** * UuidFactoryInterface defines common functionality all `UuidFactory` instances * must implement */ interface UuidFactoryInterface { /** * Creates a UUID from a byte string * * @param string $bytes A binary string * * @return UuidInterface A UuidInterface instance created from a binary * string representation * * @psalm-pure */ public function fromBytes(string $bytes): UuidInterface; /** * Creates a UUID from a DateTimeInterface instance * * @param DateTimeInterface $dateTime The date and time * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID created from a DateTimeInterface instance */ public function fromDateTime( DateTimeInterface $dateTime, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface; /** * Creates a UUID from a 128-bit integer string * * @param string $integer String representation of 128-bit integer * * @return UuidInterface A UuidInterface instance created from the string * representation of a 128-bit integer * * @psalm-pure */ public function fromInteger(string $integer): UuidInterface; /** * Creates a UUID from the string standard representation * * @param string $uuid A hexadecimal string * * @return UuidInterface A UuidInterface instance created from a hexadecimal * string representation * * @psalm-pure */ public function fromString(string $uuid): UuidInterface; /** * Returns the validator to use for the factory * * @psalm-mutation-free */ public function getValidator(): ValidatorInterface; /** * Returns a version 1 (Gregorian time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID */ public function uuid1($node = null, ?int $clockSeq = null): UuidInterface; /** * Returns a version 2 (DCE Security) UUID from a local domain, local * identifier, host ID, clock sequence, and the current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 2 UUID */ public function uuid2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface; /** * Returns a version 3 (name-based) UUID based on the MD5 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 3 UUID * * @psalm-pure */ public function uuid3($ns, string $name): UuidInterface; /** * Returns a version 4 (random) UUID * * @return UuidInterface A UuidInterface instance that represents a * version 4 UUID */ public function uuid4(): UuidInterface; /** * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 5 UUID * * @psalm-pure */ public function uuid5($ns, string $name): UuidInterface; /** * Returns a version 6 (reordered time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 6 UUID */ public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface; } PKZGU~~Math/CalculatorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Math; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\NumberInterface; /** * A calculator performs arithmetic operations on numbers * * @psalm-immutable */ interface CalculatorInterface { /** * Returns the sum of all the provided parameters * * @param NumberInterface $augend The first addend (the integer being added to) * @param NumberInterface ...$addends The additional integers to a add to the augend * * @return NumberInterface The sum of all the parameters */ public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface; /** * Returns the difference of all the provided parameters * * @param NumberInterface $minuend The integer being subtracted from * @param NumberInterface ...$subtrahends The integers to subtract from the minuend * * @return NumberInterface The difference after subtracting all parameters */ public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface; /** * Returns the product of all the provided parameters * * @param NumberInterface $multiplicand The integer to be multiplied * @param NumberInterface ...$multipliers The factors by which to multiply the multiplicand * * @return NumberInterface The product of multiplying all the provided parameters */ public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface; /** * Returns the quotient of the provided parameters divided left-to-right * * @param int $roundingMode The RoundingMode constant to use for this operation * @param int $scale The scale to use for this operation * @param NumberInterface $dividend The integer to be divided * @param NumberInterface ...$divisors The integers to divide $dividend by, in * the order in which the division operations should take place * (left-to-right) * * @return NumberInterface The quotient of dividing the provided parameters left-to-right */ public function divide( int $roundingMode, int $scale, NumberInterface $dividend, NumberInterface ...$divisors ): NumberInterface; /** * Converts a value from an arbitrary base to a base-10 integer value * * @param string $value The value to convert * @param int $base The base to convert from (i.e., 2, 16, 32, etc.) * * @return IntegerObject The base-10 integer value of the converted value */ public function fromBase(string $value, int $base): IntegerObject; /** * Converts a base-10 integer value to an arbitrary base * * @param IntegerObject $value The integer value to convert * @param int $base The base to convert to (i.e., 2, 16, 32, etc.) * * @return string The value represented in the specified base */ public function toBase(IntegerObject $value, int $base): string; /** * Converts an Integer instance to a Hexadecimal instance */ public function toHexadecimal(IntegerObject $value): Hexadecimal; /** * Converts a Hexadecimal instance to an Integer instance */ public function toInteger(Hexadecimal $value): IntegerObject; } PKZ1}lMath/BrickMathCalculator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Math; use Brick\Math\BigDecimal; use Brick\Math\BigInteger; use Brick\Math\Exception\MathException; use Brick\Math\RoundingMode as BrickMathRounding; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Type\Decimal; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\NumberInterface; /** * A calculator using the brick/math library for arbitrary-precision arithmetic * * @psalm-immutable */ final class BrickMathCalculator implements CalculatorInterface { private const ROUNDING_MODE_MAP = [ RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY, RoundingMode::UP => BrickMathRounding::UP, RoundingMode::DOWN => BrickMathRounding::DOWN, RoundingMode::CEILING => BrickMathRounding::CEILING, RoundingMode::FLOOR => BrickMathRounding::FLOOR, RoundingMode::HALF_UP => BrickMathRounding::HALF_UP, RoundingMode::HALF_DOWN => BrickMathRounding::HALF_DOWN, RoundingMode::HALF_CEILING => BrickMathRounding::HALF_CEILING, RoundingMode::HALF_FLOOR => BrickMathRounding::HALF_FLOOR, RoundingMode::HALF_EVEN => BrickMathRounding::HALF_EVEN, ]; public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface { $sum = BigInteger::of($augend->toString()); foreach ($addends as $addend) { $sum = $sum->plus($addend->toString()); } return new IntegerObject((string) $sum); } public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface { $difference = BigInteger::of($minuend->toString()); foreach ($subtrahends as $subtrahend) { $difference = $difference->minus($subtrahend->toString()); } return new IntegerObject((string) $difference); } public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface { $product = BigInteger::of($multiplicand->toString()); foreach ($multipliers as $multiplier) { $product = $product->multipliedBy($multiplier->toString()); } return new IntegerObject((string) $product); } public function divide( int $roundingMode, int $scale, NumberInterface $dividend, NumberInterface ...$divisors ): NumberInterface { $brickRounding = $this->getBrickRoundingMode($roundingMode); $quotient = BigDecimal::of($dividend->toString()); foreach ($divisors as $divisor) { $quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding); } if ($scale === 0) { return new IntegerObject((string) $quotient->toBigInteger()); } return new Decimal((string) $quotient); } public function fromBase(string $value, int $base): IntegerObject { try { return new IntegerObject((string) BigInteger::fromBase($value, $base)); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } public function toBase(IntegerObject $value, int $base): string { try { return BigInteger::of($value->toString())->toBase($base); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } public function toHexadecimal(IntegerObject $value): Hexadecimal { return new Hexadecimal($this->toBase($value, 16)); } public function toInteger(Hexadecimal $value): IntegerObject { return $this->fromBase($value->toString(), 16); } /** * Maps ramsey/uuid rounding modes to those used by brick/math */ private function getBrickRoundingMode(int $roundingMode): int { return self::ROUNDING_MODE_MAP[$roundingMode] ?? 0; } } PKZZjMath/RoundingMode.phpnu[= 0.5; otherwise, behaves * as for DOWN. Note that this is the rounding mode commonly taught at * school. */ public const HALF_UP = 5; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, * in which case round down. * * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves * as for DOWN. */ public const HALF_DOWN = 6; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, * in which case round towards positive infinity. * * If the result is positive, behaves as for HALF_UP; if negative, behaves * as for HALF_DOWN. */ public const HALF_CEILING = 7; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, * in which case round towards negative infinity. * * If the result is positive, behaves as for HALF_DOWN; if negative, behaves * as for HALF_UP. */ public const HALF_FLOOR = 8; /** * Rounds towards the "nearest neighbor" unless both neighbors are * equidistant, in which case rounds towards the even neighbor. * * Behaves as for HALF_UP if the digit to the left of the discarded fraction * is odd; behaves as for HALF_DOWN if it's even. * * Note that this is the rounding mode that statistically minimizes * cumulative error when applied repeatedly over a sequence of calculations. * It is sometimes known as "Banker's rounding", and is chiefly used in the * USA. */ public const HALF_EVEN = 9; } PKZ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface; use Ramsey\Uuid\Generator\DefaultTimeGenerator; use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorInterface; use Ramsey\Uuid\Generator\UnixTimeGenerator; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use Ramsey\Uuid\Validator\ValidatorInterface; use function bin2hex; use function hex2bin; use function pack; use function str_pad; use function strtolower; use function substr; use function substr_replace; use function unpack; use const STR_PAD_LEFT; class UuidFactory implements UuidFactoryInterface { private CodecInterface $codec; private DceSecurityGeneratorInterface $dceSecurityGenerator; private NameGeneratorInterface $nameGenerator; private NodeProviderInterface $nodeProvider; private NumberConverterInterface $numberConverter; private RandomGeneratorInterface $randomGenerator; private TimeConverterInterface $timeConverter; private TimeGeneratorInterface $timeGenerator; private TimeGeneratorInterface $unixTimeGenerator; private UuidBuilderInterface $uuidBuilder; private ValidatorInterface $validator; /** * @var bool whether the feature set was provided from outside, or we can * operate under "default" assumptions */ private bool $isDefaultFeatureSet; /** * @param FeatureSet|null $features A set of available features in the current environment */ public function __construct(?FeatureSet $features = null) { $this->isDefaultFeatureSet = $features === null; $features = $features ?: new FeatureSet(); $this->codec = $features->getCodec(); $this->dceSecurityGenerator = $features->getDceSecurityGenerator(); $this->nameGenerator = $features->getNameGenerator(); $this->nodeProvider = $features->getNodeProvider(); $this->numberConverter = $features->getNumberConverter(); $this->randomGenerator = $features->getRandomGenerator(); $this->timeConverter = $features->getTimeConverter(); $this->timeGenerator = $features->getTimeGenerator(); $this->uuidBuilder = $features->getBuilder(); $this->validator = $features->getValidator(); $this->unixTimeGenerator = $features->getUnixTimeGenerator(); } /** * Returns the codec used by this factory */ public function getCodec(): CodecInterface { return $this->codec; } /** * Sets the codec to use for this factory * * @param CodecInterface $codec A UUID encoder-decoder */ public function setCodec(CodecInterface $codec): void { $this->isDefaultFeatureSet = false; $this->codec = $codec; } /** * Returns the name generator used by this factory */ public function getNameGenerator(): NameGeneratorInterface { return $this->nameGenerator; } /** * Sets the name generator to use for this factory * * @param NameGeneratorInterface $nameGenerator A generator to generate * binary data, based on a namespace and name */ public function setNameGenerator(NameGeneratorInterface $nameGenerator): void { $this->isDefaultFeatureSet = false; $this->nameGenerator = $nameGenerator; } /** * Returns the node provider used by this factory */ public function getNodeProvider(): NodeProviderInterface { return $this->nodeProvider; } /** * Returns the random generator used by this factory */ public function getRandomGenerator(): RandomGeneratorInterface { return $this->randomGenerator; } /** * Returns the time generator used by this factory */ public function getTimeGenerator(): TimeGeneratorInterface { return $this->timeGenerator; } /** * Sets the time generator to use for this factory * * @param TimeGeneratorInterface $generator A generator to generate binary * data, based on the time */ public function setTimeGenerator(TimeGeneratorInterface $generator): void { $this->isDefaultFeatureSet = false; $this->timeGenerator = $generator; } /** * Returns the DCE Security generator used by this factory */ public function getDceSecurityGenerator(): DceSecurityGeneratorInterface { return $this->dceSecurityGenerator; } /** * Sets the DCE Security generator to use for this factory * * @param DceSecurityGeneratorInterface $generator A generator to generate * binary data, based on a local domain and local identifier */ public function setDceSecurityGenerator(DceSecurityGeneratorInterface $generator): void { $this->isDefaultFeatureSet = false; $this->dceSecurityGenerator = $generator; } /** * Returns the number converter used by this factory */ public function getNumberConverter(): NumberConverterInterface { return $this->numberConverter; } /** * Sets the random generator to use for this factory * * @param RandomGeneratorInterface $generator A generator to generate binary * data, based on some random input */ public function setRandomGenerator(RandomGeneratorInterface $generator): void { $this->isDefaultFeatureSet = false; $this->randomGenerator = $generator; } /** * Sets the number converter to use for this factory * * @param NumberConverterInterface $converter A converter to use for working * with large integers (i.e. integers greater than PHP_INT_MAX) */ public function setNumberConverter(NumberConverterInterface $converter): void { $this->isDefaultFeatureSet = false; $this->numberConverter = $converter; } /** * Returns the UUID builder used by this factory */ public function getUuidBuilder(): UuidBuilderInterface { return $this->uuidBuilder; } /** * Sets the UUID builder to use for this factory * * @param UuidBuilderInterface $builder A builder for constructing instances * of UuidInterface */ public function setUuidBuilder(UuidBuilderInterface $builder): void { $this->isDefaultFeatureSet = false; $this->uuidBuilder = $builder; } /** * @psalm-mutation-free */ public function getValidator(): ValidatorInterface { return $this->validator; } /** * Sets the validator to use for this factory * * @param ValidatorInterface $validator A validator to use for validating * whether a string is a valid UUID */ public function setValidator(ValidatorInterface $validator): void { $this->isDefaultFeatureSet = false; $this->validator = $validator; } /** * @psalm-pure */ public function fromBytes(string $bytes): UuidInterface { return $this->codec->decodeBytes($bytes); } /** * @psalm-pure */ public function fromString(string $uuid): UuidInterface { $uuid = strtolower($uuid); return $this->codec->decode($uuid); } /** * @psalm-pure */ public function fromInteger(string $integer): UuidInterface { $hex = $this->numberConverter->toHex($integer); $hex = str_pad($hex, 32, '0', STR_PAD_LEFT); return $this->fromString($hex); } public function fromDateTime( DateTimeInterface $dateTime, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { $timeProvider = new FixedTimeProvider( new Time($dateTime->format('U'), $dateTime->format('u')) ); $timeGenerator = new DefaultTimeGenerator( $this->nodeProvider, $this->timeConverter, $timeProvider ); $nodeHex = $node ? $node->toString() : null; $bytes = $timeGenerator->generate($nodeHex, $clockSeq); return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME); } /** * @psalm-pure */ public function fromHexadecimal(Hexadecimal $hex): UuidInterface { return $this->codec->decode($hex->__toString()); } /** * @inheritDoc */ public function uuid1($node = null, ?int $clockSeq = null): UuidInterface { $bytes = $this->timeGenerator->generate($node, $clockSeq); return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME); } public function uuid2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { $bytes = $this->dceSecurityGenerator->generate( $localDomain, $localIdentifier, $node, $clockSeq ); return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_DCE_SECURITY); } /** * @inheritDoc * @psalm-pure */ public function uuid3($ns, string $name): UuidInterface { return $this->uuidFromNsAndName($ns, $name, Uuid::UUID_TYPE_HASH_MD5, 'md5'); } public function uuid4(): UuidInterface { $bytes = $this->randomGenerator->generate(16); return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_RANDOM); } /** * @inheritDoc * @psalm-pure */ public function uuid5($ns, string $name): UuidInterface { return $this->uuidFromNsAndName($ns, $name, Uuid::UUID_TYPE_HASH_SHA1, 'sha1'); } public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface { $nodeHex = $node ? $node->toString() : null; $bytes = $this->timeGenerator->generate($nodeHex, $clockSeq); // Rearrange the bytes, according to the UUID version 6 specification. $v6 = $bytes[6] . $bytes[7] . $bytes[4] . $bytes[5] . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3]; $v6 = bin2hex($v6); // Drop the first four bits, while adding an empty four bits for the // version field. This allows us to reconstruct the correct time from // the bytes of this UUID. $v6Bytes = hex2bin(substr($v6, 1, 12) . '0' . substr($v6, -3)); $v6Bytes .= substr($bytes, 8); return $this->uuidFromBytesAndVersion($v6Bytes, Uuid::UUID_TYPE_REORDERED_TIME); } /** * Returns a version 7 (Unix Epoch time) UUID * * @param DateTimeInterface|null $dateTime An optional date/time from which * to create the version 7 UUID. If not provided, the UUID is generated * using the current date/time. * * @return UuidInterface A UuidInterface instance that represents a * version 7 UUID */ public function uuid7(?DateTimeInterface $dateTime = null): UuidInterface { assert($this->unixTimeGenerator instanceof UnixTimeGenerator); $bytes = $this->unixTimeGenerator->generate(null, null, $dateTime); return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_UNIX_TIME); } /** * Returns a version 8 (Custom) UUID * * The bytes provided may contain any value according to your application's * needs. Be aware, however, that other applications may not understand the * semantics of the value. * * @param string $bytes A 16-byte octet string. This is an open blob * of data that you may fill with 128 bits of information. Be aware, * however, bits 48 through 51 will be replaced with the UUID version * field, and bits 64 and 65 will be replaced with the UUID variant. You * MUST NOT rely on these bits for your application needs. * * @return UuidInterface A UuidInterface instance that represents a * version 8 UUID */ public function uuid8(string $bytes): UuidInterface { return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_CUSTOM); } /** * Returns a Uuid created from the provided byte string * * Uses the configured builder and codec and the provided byte string to * construct a Uuid object. * * @param string $bytes The byte string from which to construct a UUID * * @return UuidInterface An instance of UuidInterface, created from the * provided bytes * * @psalm-pure */ public function uuid(string $bytes): UuidInterface { /** @psalm-suppress ImpurePropertyFetch */ return $this->uuidBuilder->build($this->codec, $bytes); } /** * Returns a version 3 or 5 namespaced Uuid * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to hash together with the namespace * @param int $version The version of UUID to create (3 or 5) * @param string $hashAlgorithm The hashing algorithm to use when hashing * together the namespace and name * * @return UuidInterface An instance of UuidInterface, created by hashing * together the provided namespace and name * * @psalm-pure */ private function uuidFromNsAndName( UuidInterface | string $ns, string $name, int $version, string $hashAlgorithm ): UuidInterface { if (!($ns instanceof UuidInterface)) { $ns = $this->fromString($ns); } $bytes = $this->nameGenerator->generate($ns, $name, $hashAlgorithm); return $this->uuidFromBytesAndVersion(substr($bytes, 0, 16), $version); } /** * Returns an RFC 4122 variant Uuid, created from the provided bytes and version * * @param string $bytes The byte string to convert to a UUID * @param int $version The RFC 4122 version to apply to the UUID * * @return UuidInterface An instance of UuidInterface, created from the * byte string and version * * @psalm-pure */ private function uuidFromBytesAndVersion(string $bytes, int $version): UuidInterface { /** @var array $unpackedTime */ $unpackedTime = unpack('n*', substr($bytes, 6, 2)); $timeHi = (int) $unpackedTime[1]; $timeHiAndVersion = pack('n*', BinaryUtils::applyVersion($timeHi, $version)); /** @var array $unpackedClockSeq */ $unpackedClockSeq = unpack('n*', substr($bytes, 8, 2)); $clockSeqHi = (int) $unpackedClockSeq[1]; $clockSeqHiAndReserved = pack('n*', BinaryUtils::applyVariant($clockSeqHi)); $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2); $bytes = substr_replace($bytes, $clockSeqHiAndReserved, 8, 2); if ($this->isDefaultFeatureSet) { return LazyUuidFromString::fromBytes($bytes); } /** @psalm-suppress ImpureVariable */ return $this->uuid($bytes); } } PKZix$Converter/TimeConverterInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; /** * A time converter converts timestamps into representations that may be used * in UUIDs * * @psalm-immutable */ interface TimeConverterInterface { /** * Uses the provided seconds and micro-seconds to calculate the count of * 100-nanosecond intervals since UTC 00:00:00.00, 15 October 1582, for * RFC 4122 variant UUIDs * * @link http://tools.ietf.org/html/rfc4122#section-4.2.2 RFC 4122, § 4.2.2: Generation Details * * @param string $seconds A string representation of the number of seconds * since the Unix epoch for the time to calculate * @param string $microseconds A string representation of the micro-seconds * associated with the time to calculate * * @return Hexadecimal The full UUID timestamp as a Hexadecimal value * * @psalm-pure */ public function calculateTime(string $seconds, string $microseconds): Hexadecimal; /** * Converts a timestamp extracted from a UUID to a Unix timestamp * * @param Hexadecimal $uuidTimestamp A hexadecimal representation of a UUID * timestamp; a UUID timestamp is a count of 100-nanosecond intervals * since UTC 00:00:00.00, 15 October 1582. * * @return Time An instance of {@see Time} * * @psalm-pure */ public function convertTime(Hexadecimal $uuidTimestamp): Time; } PKZja**&Converter/NumberConverterInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter; /** * A number converter converts UUIDs from hexadecimal characters into * representations of integers and vice versa * * @psalm-immutable */ interface NumberConverterInterface { /** * Converts a hexadecimal number into an string integer representation of * the number * * The integer representation returned is a string representation of the * integer, to accommodate unsigned integers greater than PHP_INT_MAX. * * @param string $hex The hexadecimal string representation to convert * * @return string String representation of an integer * * @psalm-return numeric-string * * @psalm-pure */ public function fromHex(string $hex): string; /** * Converts a string integer representation into a hexadecimal string * representation of the number * * @param string $number A string integer representation to convert; this * must be a numeric string to accommodate unsigned integers greater * than PHP_INT_MAX. * * @return string Hexadecimal string * * @psalm-return non-empty-string * * @psalm-pure */ public function toHex(string $number): string; } PKZatww'Converter/Time/GenericTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Math\RoundingMode; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use function explode; use function str_pad; use const STR_PAD_LEFT; /** * GenericTimeConverter uses the provided calculator to calculate and convert * time values * * @psalm-immutable */ class GenericTimeConverter implements TimeConverterInterface { /** * The number of 100-nanosecond intervals from the Gregorian calendar epoch * to the Unix epoch. */ private const GREGORIAN_TO_UNIX_INTERVALS = '122192928000000000'; /** * The number of 100-nanosecond intervals in one second. */ private const SECOND_INTERVALS = '10000000'; /** * The number of 100-nanosecond intervals in one microsecond. */ private const MICROSECOND_INTERVALS = '10'; public function __construct(private CalculatorInterface $calculator) { } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { $timestamp = new Time($seconds, $microseconds); // Convert the seconds into a count of 100-nanosecond intervals. $sec = $this->calculator->multiply( $timestamp->getSeconds(), new IntegerObject(self::SECOND_INTERVALS) ); // Convert the microseconds into a count of 100-nanosecond intervals. $usec = $this->calculator->multiply( $timestamp->getMicroseconds(), new IntegerObject(self::MICROSECOND_INTERVALS) ); // Combine the seconds and microseconds intervals and add the count of // 100-nanosecond intervals from the Gregorian calendar epoch to the // Unix epoch. This gives us the correct count of 100-nanosecond // intervals since the Gregorian calendar epoch for the given seconds // and microseconds. /** @var IntegerObject $uuidTime */ $uuidTime = $this->calculator->add( $sec, $usec, new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS) ); $uuidTimeHex = str_pad( $this->calculator->toHexadecimal($uuidTime)->toString(), 16, '0', STR_PAD_LEFT ); return new Hexadecimal($uuidTimeHex); } public function convertTime(Hexadecimal $uuidTimestamp): Time { // From the total, subtract the number of 100-nanosecond intervals from // the Gregorian calendar epoch to the Unix epoch. This gives us the // number of 100-nanosecond intervals from the Unix epoch, which also // includes the microtime. $epochNanoseconds = $this->calculator->subtract( $this->calculator->toInteger($uuidTimestamp), new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS) ); // Convert the 100-nanosecond intervals into seconds and microseconds. $unixTimestamp = $this->calculator->divide( RoundingMode::HALF_UP, 6, $epochNanoseconds, new IntegerObject(self::SECOND_INTERVALS) ); $split = explode('.', (string) $unixTimestamp, 2); return new Time($split[0], $split[1] ?? 0); } } PKZs(gg(Converter/Time/DegradedTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; /** * @deprecated DegradedTimeConverter is no longer necessary for converting * time on 32-bit systems. Transition to {@see GenericTimeConverter}. * * @psalm-immutable */ class DegradedTimeConverter extends BigNumberTimeConverter { } PKZXz#Converter/Time/PhpTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use function count; use function dechex; use function explode; use function is_float; use function is_int; use function str_pad; use function strlen; use function substr; use const STR_PAD_LEFT; use const STR_PAD_RIGHT; /** * PhpTimeConverter uses built-in PHP functions and standard math operations * available to the PHP programming language to provide facilities for * converting parts of time into representations that may be used in UUIDs * * @psalm-immutable */ class PhpTimeConverter implements TimeConverterInterface { /** * The number of 100-nanosecond intervals from the Gregorian calendar epoch * to the Unix epoch. */ private const GREGORIAN_TO_UNIX_INTERVALS = 0x01b21dd213814000; /** * The number of 100-nanosecond intervals in one second. */ private const SECOND_INTERVALS = 10000000; /** * The number of 100-nanosecond intervals in one microsecond. */ private const MICROSECOND_INTERVALS = 10; private int $phpPrecision; private CalculatorInterface $calculator; private TimeConverterInterface $fallbackConverter; public function __construct( ?CalculatorInterface $calculator = null, ?TimeConverterInterface $fallbackConverter = null ) { if ($calculator === null) { $calculator = new BrickMathCalculator(); } if ($fallbackConverter === null) { $fallbackConverter = new GenericTimeConverter($calculator); } $this->calculator = $calculator; $this->fallbackConverter = $fallbackConverter; $this->phpPrecision = (int) ini_get('precision'); } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { $seconds = new IntegerObject($seconds); $microseconds = new IntegerObject($microseconds); // Calculate the count of 100-nanosecond intervals since the Gregorian // calendar epoch for the given seconds and microseconds. $uuidTime = ((int) $seconds->toString() * self::SECOND_INTERVALS) + ((int) $microseconds->toString() * self::MICROSECOND_INTERVALS) + self::GREGORIAN_TO_UNIX_INTERVALS; // Check to see whether we've overflowed the max/min integer size. // If so, we will default to a different time converter. /** @psalm-suppress RedundantCondition */ if (!is_int($uuidTime)) { return $this->fallbackConverter->calculateTime( $seconds->toString(), $microseconds->toString() ); } return new Hexadecimal(str_pad(dechex($uuidTime), 16, '0', STR_PAD_LEFT)); } public function convertTime(Hexadecimal $uuidTimestamp): Time { $timestamp = $this->calculator->toInteger($uuidTimestamp); // Convert the 100-nanosecond intervals into seconds and microseconds. $splitTime = $this->splitTime( ((int) $timestamp->toString() - self::GREGORIAN_TO_UNIX_INTERVALS) / self::SECOND_INTERVALS ); if (count($splitTime) === 0) { return $this->fallbackConverter->convertTime($uuidTimestamp); } return new Time($splitTime['sec'], $splitTime['usec']); } /** * @param float|int $time The time to split into seconds and microseconds * * @return string[] */ private function splitTime(float | int $time): array { $split = explode('.', (string) $time, 2); // If the $time value is a float but $split only has 1 element, then the // float math was rounded up to the next second, so we want to return // an empty array to allow use of the fallback converter. if (is_float($time) && count($split) === 1) { return []; } if (count($split) === 1) { return [ 'sec' => $split[0], 'usec' => '0', ]; } // If the microseconds are less than six characters AND the length of // the number is greater than or equal to the PHP precision, then it's // possible that we lost some precision for the microseconds. Return an // empty array, so that we can choose to use the fallback converter. if (strlen($split[1]) < 6 && strlen((string) $time) >= $this->phpPrecision) { return []; } $microseconds = $split[1]; // Ensure the microseconds are no longer than 6 digits. If they are, // truncate the number to the first 6 digits and round up, if needed. if (strlen($microseconds) > 6) { $roundingDigit = (int) substr($microseconds, 6, 1); $microseconds = (int) substr($microseconds, 0, 6); if ($roundingDigit >= 5) { $microseconds++; } } return [ 'sec' => $split[0], 'usec' => str_pad((string) $microseconds, 6, '0', STR_PAD_RIGHT), ]; } } PKZ V $Converter/Time/UnixTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Math\RoundingMode; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; use function explode; use function str_pad; use const STR_PAD_LEFT; /** * UnixTimeConverter converts Unix Epoch timestamps to/from hexadecimal values * consisting of milliseconds elapsed since the Unix Epoch * * @psalm-immutable */ class UnixTimeConverter implements TimeConverterInterface { private const MILLISECONDS = 1000; public function __construct(private CalculatorInterface $calculator) { } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { $timestamp = new Time($seconds, $microseconds); // Convert the seconds into milliseconds. $sec = $this->calculator->multiply( $timestamp->getSeconds(), new IntegerObject(self::MILLISECONDS), ); // Convert the microseconds into milliseconds; the scale is zero because // we need to discard the fractional part. $usec = $this->calculator->divide( RoundingMode::DOWN, // Always round down to stay in the previous millisecond. 0, $timestamp->getMicroseconds(), new IntegerObject(self::MILLISECONDS), ); /** @var IntegerObject $unixTime */ $unixTime = $this->calculator->add($sec, $usec); $unixTimeHex = str_pad( $this->calculator->toHexadecimal($unixTime)->toString(), 12, '0', STR_PAD_LEFT ); return new Hexadecimal($unixTimeHex); } public function convertTime(Hexadecimal $uuidTimestamp): Time { $milliseconds = $this->calculator->toInteger($uuidTimestamp); $unixTimestamp = $this->calculator->divide( RoundingMode::HALF_UP, 6, $milliseconds, new IntegerObject(self::MILLISECONDS) ); $split = explode('.', (string) $unixTimestamp, 2); return new Time($split[0], $split[1] ?? '0'); } } PKZ[,,)Converter/Time/BigNumberTimeConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Time; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; /** * Previously used to integrate moontoast/math as a bignum arithmetic library, * BigNumberTimeConverter is deprecated in favor of GenericTimeConverter * * @deprecated Transition to {@see GenericTimeConverter}. * * @psalm-immutable */ class BigNumberTimeConverter implements TimeConverterInterface { private TimeConverterInterface $converter; public function __construct() { $this->converter = new GenericTimeConverter(new BrickMathCalculator()); } public function calculateTime(string $seconds, string $microseconds): Hexadecimal { return $this->converter->calculateTime($seconds, $microseconds); } public function convertTime(Hexadecimal $uuidTimestamp): Time { return $this->converter->convertTime($uuidTimestamp); } } PKZTgd'Converter/Number/BigNumberConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Number; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Math\BrickMathCalculator; /** * Previously used to integrate moontoast/math as a bignum arithmetic library, * BigNumberConverter is deprecated in favor of GenericNumberConverter * * @deprecated Transition to {@see GenericNumberConverter}. * * @psalm-immutable */ class BigNumberConverter implements NumberConverterInterface { private NumberConverterInterface $converter; public function __construct() { $this->converter = new GenericNumberConverter(new BrickMathCalculator()); } /** * @inheritDoc * @psalm-pure */ public function fromHex(string $hex): string { return $this->converter->fromHex($hex); } /** * @inheritDoc * @psalm-pure */ public function toHex(string $number): string { return $this->converter->toHex($number); } } PKZvWnn,Converter/Number/DegradedNumberConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Number; /** * @deprecated DegradedNumberConverter is no longer necessary for converting * numbers on 32-bit systems. Transition to {@see GenericNumberConverter}. * * @psalm-immutable */ class DegradedNumberConverter extends BigNumberConverter { } PKZZ'+Converter/Number/GenericNumberConverter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Converter\Number; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Math\CalculatorInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * GenericNumberConverter uses the provided calculator to convert decimal * numbers to and from hexadecimal values * * @psalm-immutable */ class GenericNumberConverter implements NumberConverterInterface { public function __construct(private CalculatorInterface $calculator) { } /** * @inheritDoc * @psalm-pure * @psalm-return numeric-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function fromHex(string $hex): string { return $this->calculator->fromBase($hex, 16)->toString(); } /** * @inheritDoc * @psalm-pure * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function toHex(string $number): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $this->calculator->toBase(new IntegerObject($number), 16); } } PKZCodec/GuidStringCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Guid\Guid; use Ramsey\Uuid\UuidInterface; use function bin2hex; use function sprintf; use function substr; /** * GuidStringCodec encodes and decodes globally unique identifiers (GUID) * * @see Guid * * @psalm-immutable */ class GuidStringCodec extends StringCodec { public function encode(UuidInterface $uuid): string { $hex = bin2hex($uuid->getFields()->getBytes()); /** @var non-empty-string */ return sprintf( '%02s%02s%02s%02s-%02s%02s-%02s%02s-%04s-%012s', substr($hex, 6, 2), substr($hex, 4, 2), substr($hex, 2, 2), substr($hex, 0, 2), substr($hex, 10, 2), substr($hex, 8, 2), substr($hex, 14, 2), substr($hex, 12, 2), substr($hex, 16, 4), substr($hex, 20), ); } public function decode(string $encodedUuid): UuidInterface { $bytes = $this->getBytes($encodedUuid); return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } public function decodeBytes(string $bytes): UuidInterface { // Specifically call parent::decode to preserve correct byte order return parent::decode(bin2hex($bytes)); } /** * Swaps bytes according to the GUID rules */ private function swapBytes(string $bytes): string { return $bytes[3] . $bytes[2] . $bytes[1] . $bytes[0] . $bytes[5] . $bytes[4] . $bytes[7] . $bytes[6] . substr($bytes, 8); } } PKZo c c Codec/StringCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function bin2hex; use function hex2bin; use function implode; use function sprintf; use function str_replace; use function strlen; use function substr; /** * StringCodec encodes and decodes RFC 4122 UUIDs * * @link http://tools.ietf.org/html/rfc4122 * * @psalm-immutable */ class StringCodec implements CodecInterface { /** * Constructs a StringCodec * * @param UuidBuilderInterface $builder The builder to use when encoding UUIDs */ public function __construct(private UuidBuilderInterface $builder) { } public function encode(UuidInterface $uuid): string { $hex = bin2hex($uuid->getFields()->getBytes()); /** @var non-empty-string */ return sprintf( '%08s-%04s-%04s-%04s-%012s', substr($hex, 0, 8), substr($hex, 8, 4), substr($hex, 12, 4), substr($hex, 16, 4), substr($hex, 20), ); } /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encodeBinary(UuidInterface $uuid): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $uuid->getFields()->getBytes(); } /** * @throws InvalidUuidStringException * * @inheritDoc */ public function decode(string $encodedUuid): UuidInterface { return $this->builder->build($this, $this->getBytes($encodedUuid)); } public function decodeBytes(string $bytes): UuidInterface { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( '$bytes string should contain 16 characters.' ); } return $this->builder->build($this, $bytes); } /** * Returns the UUID builder */ protected function getBuilder(): UuidBuilderInterface { return $this->builder; } /** * Returns a byte string of the UUID */ protected function getBytes(string $encodedUuid): string { $parsedUuid = str_replace( ['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}', '-'], '', $encodedUuid ); $components = [ substr($parsedUuid, 0, 8), substr($parsedUuid, 8, 4), substr($parsedUuid, 12, 4), substr($parsedUuid, 16, 4), substr($parsedUuid, 20), ]; if (!Uuid::isValid(implode('-', $components))) { throw new InvalidUuidStringException( 'Invalid UUID string: ' . $encodedUuid ); } return (string) hex2bin($parsedUuid); } } PKZLCodec/OrderedTimeCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function strlen; use function substr; /** * OrderedTimeCodec encodes and decodes a UUID, optimizing the byte order for * more efficient storage * * For binary representations of version 1 UUID, this codec may be used to * reorganize the time fields, making the UUID closer to sequential when storing * the bytes. According to Percona, this optimization can improve database * INSERTs and SELECTs using the UUID column as a key. * * The string representation of the UUID will remain unchanged. Only the binary * representation is reordered. * * **PLEASE NOTE:** Binary representations of UUIDs encoded with this codec must * be decoded with this codec. Decoding using another codec can result in * malformed UUIDs. * * @link https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ Storing UUID Values in MySQL * * @psalm-immutable */ class OrderedTimeCodec extends StringCodec { /** * Returns a binary string representation of a UUID, with the timestamp * fields rearranged for optimized storage * * @inheritDoc * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encodeBinary(UuidInterface $uuid): string { if ( !($uuid->getFields() instanceof Rfc4122FieldsInterface) || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME ) { throw new InvalidArgumentException( 'Expected RFC 4122 version 1 (time-based) UUID' ); } $bytes = $uuid->getFields()->getBytes(); /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $bytes[6] . $bytes[7] . $bytes[4] . $bytes[5] . $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3] . substr($bytes, 8); } /** * Returns a UuidInterface derived from an ordered-time binary string * representation * * @throws InvalidArgumentException if $bytes is an invalid length * * @inheritDoc */ public function decodeBytes(string $bytes): UuidInterface { if (strlen($bytes) !== 16) { throw new InvalidArgumentException( '$bytes string should contain 16 characters.' ); } // Rearrange the bytes to their original order. $rearrangedBytes = $bytes[4] . $bytes[5] . $bytes[6] . $bytes[7] . $bytes[2] . $bytes[3] . $bytes[0] . $bytes[1] . substr($bytes, 8); $uuid = parent::decodeBytes($rearrangedBytes); if ( !($uuid->getFields() instanceof Rfc4122FieldsInterface) || $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME ) { throw new UnsupportedOperationException( 'Attempting to decode a non-time-based UUID using ' . 'OrderedTimeCodec' ); } return $uuid; } } PKZhCodec/CodecInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\UuidInterface; /** * A codec encodes and decodes a UUID according to defined rules * * @psalm-immutable */ interface CodecInterface { /** * Returns a hexadecimal string representation of a UuidInterface * * @param UuidInterface $uuid The UUID for which to create a hexadecimal * string representation * * @return string Hexadecimal string representation of a UUID * * @psalm-return non-empty-string */ public function encode(UuidInterface $uuid): string; /** * Returns a binary string representation of a UuidInterface * * @param UuidInterface $uuid The UUID for which to create a binary string * representation * * @return string Binary string representation of a UUID * * @psalm-return non-empty-string */ public function encodeBinary(UuidInterface $uuid): string; /** * Returns a UuidInterface derived from a hexadecimal string representation * * @param string $encodedUuid The hexadecimal string representation to * convert into a UuidInterface instance * * @return UuidInterface An instance of a UUID decoded from a hexadecimal * string representation */ public function decode(string $encodedUuid): UuidInterface; /** * Returns a UuidInterface derived from a binary string representation * * @param string $bytes The binary string representation to convert into a * UuidInterface instance * * @return UuidInterface An instance of a UUID decoded from a binary string * representation */ public function decodeBytes(string $bytes): UuidInterface; } PKZ{N N !Codec/TimestampFirstCombCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; use Ramsey\Uuid\Exception\InvalidUuidStringException; use Ramsey\Uuid\UuidInterface; use function bin2hex; use function sprintf; use function substr; use function substr_replace; /** * TimestampFirstCombCodec encodes and decodes COMBs, with the timestamp as the * first 48 bits * * In contrast with the TimestampLastCombCodec, the TimestampFirstCombCodec * adds the timestamp to the first 48 bits of the COMB. To generate a * timestamp-first COMB, set the TimestampFirstCombCodec as the codec, along * with the CombGenerator as the random generator. * * ``` php * $factory = new UuidFactory(); * * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder())); * * $factory->setRandomGenerator(new CombGenerator( * $factory->getRandomGenerator(), * $factory->getNumberConverter() * )); * * $timestampFirstComb = $factory->uuid4(); * ``` * * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys * * @psalm-immutable */ class TimestampFirstCombCodec extends StringCodec { /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encode(UuidInterface $uuid): string { $bytes = $this->swapBytes($uuid->getFields()->getBytes()); return sprintf( '%08s-%04s-%04s-%04s-%012s', bin2hex(substr($bytes, 0, 4)), bin2hex(substr($bytes, 4, 2)), bin2hex(substr($bytes, 6, 2)), bin2hex(substr($bytes, 8, 2)), bin2hex(substr($bytes, 10)) ); } /** * @psalm-return non-empty-string * @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty * @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty */ public function encodeBinary(UuidInterface $uuid): string { /** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */ return $this->swapBytes($uuid->getFields()->getBytes()); } /** * @throws InvalidUuidStringException * * @inheritDoc */ public function decode(string $encodedUuid): UuidInterface { $bytes = $this->getBytes($encodedUuid); return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } public function decodeBytes(string $bytes): UuidInterface { return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } /** * Swaps bytes according to the timestamp-first COMB rules */ private function swapBytes(string $bytes): string { $first48Bits = substr($bytes, 0, 6); $last48Bits = substr($bytes, -6); $bytes = substr_replace($bytes, $last48Bits, 0, 6); $bytes = substr_replace($bytes, $first48Bits, -6); return $bytes; } } PKZ8Gb=NN Codec/TimestampLastCombCodec.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Codec; /** * TimestampLastCombCodec encodes and decodes COMBs, with the timestamp as the * last 48 bits * * The CombGenerator when used with the StringCodec (and, by proxy, the * TimestampLastCombCodec) adds the timestamp to the last 48 bits of the COMB. * The TimestampLastCombCodec is provided for the sake of consistency. In * practice, it is identical to the standard StringCodec but, it may be used * with the CombGenerator for additional context when reading code. * * Consider the following code. By default, the codec used by UuidFactory is the * StringCodec, but here, we explicitly set the TimestampLastCombCodec. It is * redundant, but it is clear that we intend this COMB to be generated with the * timestamp appearing at the end. * * ``` php * $factory = new UuidFactory(); * * $factory->setCodec(new TimestampLastCombCodec($factory->getUuidBuilder())); * * $factory->setRandomGenerator(new CombGenerator( * $factory->getRandomGenerator(), * $factory->getNumberConverter() * )); * * $timestampLastComb = $factory->uuid4(); * ``` * * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys * * @psalm-immutable */ class TimestampLastCombCodec extends StringCodec { } PKZfT66"Provider/TimeProviderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider; use Ramsey\Uuid\Type\Time; /** * A time provider retrieves the current time */ interface TimeProviderInterface { /** * Returns a time object */ public function getTime(): Time; } PKZd)Provider/DceSecurityProviderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider; use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * A DCE provider provides access to local domain identifiers for version 2, * DCE Security, UUIDs * * @see UuidV2 */ interface DceSecurityProviderInterface { /** * Returns a user identifier for the system * * @link https://en.wikipedia.org/wiki/User_identifier User identifier */ public function getUid(): IntegerObject; /** * Returns a group identifier for the system * * @link https://en.wikipedia.org/wiki/Group_identifier Group identifier */ public function getGid(): IntegerObject; } PKZv$Provider/Time/SystemTimeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Time; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Type\Time; use function gettimeofday; /** * SystemTimeProvider retrieves the current time using built-in PHP functions */ class SystemTimeProvider implements TimeProviderInterface { public function getTime(): Time { $time = gettimeofday(); return new Time($time['sec'], $time['usec']); } } PKZXww#Provider/Time/FixedTimeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Time; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Type\Time; /** * FixedTimeProvider uses a known time to provide the time * * This provider allows the use of a previously-generated, or known, time * when generating time-based UUIDs. */ class FixedTimeProvider implements TimeProviderInterface { public function __construct(private Time $time) { } /** * Sets the `usec` component of the time * * @param int|string|IntegerObject $value The `usec` value to set */ public function setUsec($value): void { $this->time = new Time($this->time->getSeconds(), $value); } /** * Sets the `sec` component of the time * * @param int|string|IntegerObject $value The `sec` value to set */ public function setSec($value): void { $this->time = new Time($value, $this->time->getMicroseconds()); } public function getTime(): Time { return $this->time; } } PKZCo%XX$Provider/Node/StaticNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use function dechex; use function hexdec; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * StaticNodeProvider provides a static node value with the multicast bit set * * @link http://tools.ietf.org/html/rfc4122#section-4.5 RFC 4122, § 4.5: Node IDs that Do Not Identify the Host */ class StaticNodeProvider implements NodeProviderInterface { private Hexadecimal $node; /** * @param Hexadecimal $node The static node value to use */ public function __construct(Hexadecimal $node) { if (strlen($node->toString()) > 12) { throw new InvalidArgumentException( 'Static node value cannot be greater than 12 hexadecimal characters' ); } $this->node = $this->setMulticastBit($node); } public function getNode(): Hexadecimal { return $this->node; } /** * Set the multicast bit for the static node value */ private function setMulticastBit(Hexadecimal $node): Hexadecimal { $nodeHex = str_pad($node->toString(), 12, '0', STR_PAD_LEFT); $firstOctet = substr($nodeHex, 0, 2); $firstOctet = str_pad( dechex(hexdec($firstOctet) | 0x01), 2, '0', STR_PAD_LEFT ); return new Hexadecimal($firstOctet . substr($nodeHex, 2)); } } PKZwk6ZZ(Provider/Node/NodeProviderCollection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Collection\AbstractCollection; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; /** * A collection of NodeProviderInterface objects * * @deprecated this class has been deprecated, and will be removed in 5.0.0. The use-case for this class comes from * a pre-`phpstan/phpstan` and pre-`vimeo/psalm` ecosystem, in which type safety had to be mostly enforced * at runtime: that is no longer necessary, now that you can safely verify your code to be correct, and use * more generic types like `iterable` instead. * * @extends AbstractCollection */ class NodeProviderCollection extends AbstractCollection { public function getType(): string { return NodeProviderInterface::class; } /** * Re-constructs the object from its serialized form * * @param string $serialized The serialized PHP string to unserialize into * a UuidInterface instance * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress RedundantConditionGivenDocblockType */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, [ 'allowed_classes' => [ Hexadecimal::class, RandomNodeProvider::class, StaticNodeProvider::class, SystemNodeProvider::class, ], ]); $this->data = array_filter( $data, function ($unserialized): bool { return $unserialized instanceof NodeProviderInterface; } ); } } PKZ}1T$Provider/Node/SystemNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\NodeException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use function array_filter; use function array_map; use function array_walk; use function count; use function ob_get_clean; use function ob_start; use function preg_match; use function preg_match_all; use function reset; use function str_contains; use function str_replace; use function strtolower; use function strtoupper; use function substr; use const GLOB_NOSORT; use const PREG_PATTERN_ORDER; /** * SystemNodeProvider retrieves the system node ID, if possible * * The system node ID, or host ID, is often the same as the MAC address for a * network interface on the host. */ class SystemNodeProvider implements NodeProviderInterface { /** * Pattern to match nodes in ifconfig and ipconfig output. */ private const IFCONFIG_PATTERN = '/[^:]([0-9a-f]{2}([:-])[0-9a-f]{2}(\2[0-9a-f]{2}){4})[^:]/i'; /** * Pattern to match nodes in sysfs stream output. */ private const SYSFS_PATTERN = '/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i'; public function getNode(): Hexadecimal { $node = $this->getNodeFromSystem(); if ($node === '') { throw new NodeException( 'Unable to fetch a node for this system' ); } return new Hexadecimal($node); } /** * Returns the system node, if it can find it */ protected function getNodeFromSystem(): string { static $node = null; if ($node !== null) { return (string) $node; } // First, try a Linux-specific approach. $node = $this->getSysfs(); if ($node === '') { // Search ifconfig output for MAC addresses & return the first one. $node = $this->getIfconfig(); } $node = str_replace([':', '-'], '', $node); return $node; } /** * Returns the network interface configuration for the system * * @codeCoverageIgnore */ protected function getIfconfig(): string { $disabledFunctions = strtolower((string) ini_get('disable_functions')); if (str_contains($disabledFunctions, 'passthru')) { return ''; } /** * @psalm-suppress UnnecessaryVarAnnotation * @var string $phpOs */ $phpOs = constant('PHP_OS'); ob_start(); switch (strtoupper(substr($phpOs, 0, 3))) { case 'WIN': passthru('ipconfig /all 2>&1'); break; case 'DAR': passthru('ifconfig 2>&1'); break; case 'FRE': passthru('netstat -i -f link 2>&1'); break; case 'LIN': default: passthru('netstat -ie 2>&1'); break; } $ifconfig = (string) ob_get_clean(); if (preg_match_all(self::IFCONFIG_PATTERN, $ifconfig, $matches, PREG_PATTERN_ORDER)) { foreach ($matches[1] as $iface) { if ($iface !== '00:00:00:00:00:00' && $iface !== '00-00-00-00-00-00') { return $iface; } } } return ''; } /** * Returns MAC address from the first system interface via the sysfs interface */ protected function getSysfs(): string { $mac = ''; /** * @psalm-suppress UnnecessaryVarAnnotation * @var string $phpOs */ $phpOs = constant('PHP_OS'); if (strtoupper($phpOs) === 'LINUX') { $addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT); if ($addressPaths === false || count($addressPaths) === 0) { return ''; } /** @var array $macs */ $macs = []; array_walk($addressPaths, function (string $addressPath) use (&$macs): void { if (is_readable($addressPath)) { $macs[] = file_get_contents($addressPath); } }); /** @var callable $trim */ $trim = 'trim'; $macs = array_map($trim, $macs); // Remove invalid entries. $macs = array_filter($macs, function (string $address) { return $address !== '00:00:00:00:00:00' && preg_match(self::SYSFS_PATTERN, $address); }); /** @var string|bool $mac */ $mac = reset($macs); } return (string) $mac; } } PKZMaa&Provider/Node/FallbackNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\NodeException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; /** * FallbackNodeProvider retrieves the system node ID by stepping through a list * of providers until a node ID can be obtained */ class FallbackNodeProvider implements NodeProviderInterface { /** * @param iterable $providers Array of node providers */ public function __construct(private iterable $providers) { } public function getNode(): Hexadecimal { $lastProviderException = null; foreach ($this->providers as $provider) { try { return $provider->getNode(); } catch (NodeException $exception) { $lastProviderException = $exception; continue; } } throw new NodeException( 'Unable to find a suitable node provider', 0, $lastProviderException ); } } PKZ $Provider/Node/RandomNodeProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Node; use Ramsey\Uuid\Exception\RandomSourceException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use Throwable; use function bin2hex; use function dechex; use function hex2bin; use function hexdec; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * RandomNodeProvider generates a random node ID * * @link http://tools.ietf.org/html/rfc4122#section-4.5 RFC 4122, § 4.5: Node IDs that Do Not Identify the Host */ class RandomNodeProvider implements NodeProviderInterface { public function getNode(): Hexadecimal { try { $nodeBytes = random_bytes(6); } catch (Throwable $exception) { throw new RandomSourceException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } // Split the node bytes for math on 32-bit systems. $nodeMsb = substr($nodeBytes, 0, 3); $nodeLsb = substr($nodeBytes, 3); // Set the multicast bit; see RFC 4122, section 4.5. $nodeMsb = hex2bin( str_pad( dechex(hexdec(bin2hex($nodeMsb)) | 0x010000), 6, '0', STR_PAD_LEFT ) ); // Recombine the node bytes. $node = $nodeMsb . $nodeLsb; return new Hexadecimal(str_pad(bin2hex($node), 12, '0', STR_PAD_LEFT)); } } PKZ`"Provider/NodeProviderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider; use Ramsey\Uuid\Type\Hexadecimal; /** * A node provider retrieves or generates a node ID */ interface NodeProviderInterface { /** * Returns a node ID * * @return Hexadecimal The node ID as a hexadecimal string */ public function getNode(): Hexadecimal; } PKZ**Provider/Dce/SystemDceSecurityProvider.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Provider\Dce; use Ramsey\Uuid\Exception\DceSecurityException; use Ramsey\Uuid\Provider\DceSecurityProviderInterface; use Ramsey\Uuid\Type\Integer as IntegerObject; use function escapeshellarg; use function preg_split; use function str_getcsv; use function strrpos; use function strtolower; use function strtoupper; use function substr; use function trim; use const PREG_SPLIT_NO_EMPTY; /** * SystemDceSecurityProvider retrieves the user or group identifiers from the system */ class SystemDceSecurityProvider implements DceSecurityProviderInterface { /** * @throws DceSecurityException if unable to get a user identifier * * @inheritDoc */ public function getUid(): IntegerObject { /** @var int|float|string|IntegerObject|null $uid */ static $uid = null; if ($uid instanceof IntegerObject) { return $uid; } if ($uid === null) { $uid = $this->getSystemUid(); } if ($uid === '') { throw new DceSecurityException( 'Unable to get a user identifier using the system DCE ' . 'Security provider; please provide a custom identifier or ' . 'use a different provider' ); } $uid = new IntegerObject($uid); return $uid; } /** * @throws DceSecurityException if unable to get a group identifier * * @inheritDoc */ public function getGid(): IntegerObject { /** @var int|float|string|IntegerObject|null $gid */ static $gid = null; if ($gid instanceof IntegerObject) { return $gid; } if ($gid === null) { $gid = $this->getSystemGid(); } if ($gid === '') { throw new DceSecurityException( 'Unable to get a group identifier using the system DCE ' . 'Security provider; please provide a custom identifier or ' . 'use a different provider' ); } $gid = new IntegerObject($gid); return $gid; } /** * Returns the UID from the system */ private function getSystemUid(): string { if (!$this->hasShellExec()) { return ''; } return match ($this->getOs()) { 'WIN' => $this->getWindowsUid(), default => trim((string) shell_exec('id -u')), }; } /** * Returns the GID from the system */ private function getSystemGid(): string { if (!$this->hasShellExec()) { return ''; } return match ($this->getOs()) { 'WIN' => $this->getWindowsGid(), default => trim((string) shell_exec('id -g')), }; } /** * Returns true if shell_exec() is available for use */ private function hasShellExec(): bool { $disabledFunctions = strtolower((string) ini_get('disable_functions')); return !str_contains($disabledFunctions, 'shell_exec'); } /** * Returns the PHP_OS string */ private function getOs(): string { /** * @psalm-suppress UnnecessaryVarAnnotation * @var string $phpOs */ $phpOs = constant('PHP_OS'); return strtoupper(substr($phpOs, 0, 3)); } /** * Returns the user identifier for a user on a Windows system * * Windows does not have the same concept as an effective POSIX UID for the * running script. Instead, each user is uniquely identified by an SID * (security identifier). The SID includes three 32-bit unsigned integers * that make up a unique domain identifier, followed by an RID (relative * identifier) that we will use as the UID. The primary caveat is that this * UID may not be unique to the system, since it is, instead, unique to the * domain. * * @link https://www.lifewire.com/what-is-an-sid-number-2626005 What Is an SID Number? * @link https://bit.ly/30vE7NM Well-known SID Structures * @link https://bit.ly/2FWcYKJ Well-known security identifiers in Windows operating systems * @link https://www.windows-commandline.com/get-sid-of-user/ Get SID of user */ private function getWindowsUid(): string { $response = shell_exec('whoami /user /fo csv /nh'); if ($response === null) { return ''; } $sid = str_getcsv(trim((string) $response))[1] ?? ''; if (($lastHyphen = strrpos($sid, '-')) === false) { return ''; } return trim(substr($sid, $lastHyphen + 1)); } /** * Returns a group identifier for a user on a Windows system * * Since Windows does not have the same concept as an effective POSIX GID * for the running script, we will get the local group memberships for the * user running the script. Then, we will get the SID (security identifier) * for the first group that appears in that list. Finally, we will return * the RID (relative identifier) for the group and use that as the GID. * * @link https://www.windows-commandline.com/list-of-user-groups-command-line/ List of user groups command line */ private function getWindowsGid(): string { $response = shell_exec('net user %username% | findstr /b /i "Local Group Memberships"'); if ($response === null) { return ''; } /** @var string[] $userGroups */ $userGroups = preg_split('/\s{2,}/', (string) $response, -1, PREG_SPLIT_NO_EMPTY); $firstGroup = trim($userGroups[1] ?? '', "* \t\n\r\0\x0B"); if ($firstGroup === '') { return ''; } $response = shell_exec('wmic group get name,sid | findstr /b /i ' . escapeshellarg($firstGroup)); if ($response === null) { return ''; } /** @var string[] $userGroup */ $userGroup = preg_split('/\s{2,}/', (string) $response, -1, PREG_SPLIT_NO_EMPTY); $sid = $userGroup[1] ?? ''; if (($lastHyphen = strrpos($sid, '-')) === false) { return ''; } return trim(substr($sid, $lastHyphen + 1)); } } PKZo functions.phpnu[ * @license http://opensource.org/licenses/MIT MIT * phpcs:disable Squiz.Functions.GlobalFunction */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * Returns a version 1 (Gregorian time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return non-empty-string Version 1 UUID as a string */ function v1($node = null, ?int $clockSeq = null): string { return Uuid::uuid1($node, $clockSeq)->toString(); } /** * Returns a version 2 (DCE Security) UUID from a local domain, local * identifier, host ID, clock sequence, and the current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return non-empty-string Version 2 UUID as a string */ function v2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): string { return Uuid::uuid2($localDomain, $localIdentifier, $node, $clockSeq)->toString(); } /** * Returns a version 3 (name-based) UUID based on the MD5 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * * @return non-empty-string Version 3 UUID as a string * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ function v3($ns, string $name): string { return Uuid::uuid3($ns, $name)->toString(); } /** * Returns a version 4 (random) UUID * * @return non-empty-string Version 4 UUID as a string */ function v4(): string { return Uuid::uuid4()->toString(); } /** * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * * @return non-empty-string Version 5 UUID as a string * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ function v5($ns, string $name): string { return Uuid::uuid5($ns, $name)->toString(); } /** * Returns a version 6 (reordered time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return non-empty-string Version 6 UUID as a string */ function v6(?Hexadecimal $node = null, ?int $clockSeq = null): string { return Uuid::uuid6($node, $clockSeq)->toString(); } /** * Returns a version 7 (Unix Epoch time) UUID * * @param DateTimeInterface|null $dateTime An optional date/time from which * to create the version 7 UUID. If not provided, the UUID is generated * using the current date/time. * * @return non-empty-string Version 7 UUID as a string */ function v7(?DateTimeInterface $dateTime = null): string { return Uuid::uuid7($dateTime)->toString(); } /** * Returns a version 8 (custom) UUID * * The bytes provided may contain any value according to your application's * needs. Be aware, however, that other applications may not understand the * semantics of the value. * * @param string $bytes A 16-byte octet string. This is an open blob * of data that you may fill with 128 bits of information. Be aware, * however, bits 48 through 51 will be replaced with the UUID version * field, and bits 64 and 65 will be replaced with the UUID variant. You * MUST NOT rely on these bits for your application needs. * * @return non-empty-string Version 8 UUID as a string */ function v8(string $bytes): string { return Uuid::uuid8($bytes)->toString(); } PKZyy"Fields/SerializableFieldsTrait.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Fields; use ValueError; use function base64_decode; use function sprintf; use function strlen; /** * Provides common serialization functionality to fields * * @psalm-immutable */ trait SerializableFieldsTrait { /** * @param string $bytes The bytes that comprise the fields */ abstract public function __construct(string $bytes); /** * Returns the bytes that comprise the fields */ abstract public function getBytes(): string; /** * Returns a string representation of object */ public function serialize(): string { return $this->getBytes(); } /** * @return array{bytes: string} */ public function __serialize(): array { return ['bytes' => $this->getBytes()]; } /** * Constructs the object from a serialized string representation * * @param string $data The serialized string representation of the object * * @psalm-suppress UnusedMethodCall */ public function unserialize(string $data): void { if (strlen($data) === 16) { $this->__construct($data); } else { $this->__construct(base64_decode($data)); } } /** * @param array{bytes?: string} $data * * @psalm-suppress UnusedMethodCall */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['bytes'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['bytes']); } } PKZt4Fields/FieldsInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Fields; use Serializable; /** * UUIDs are comprised of unsigned integers, the bytes of which are separated * into fields and arranged in a particular layout defined by the specification * for the variant * * @psalm-immutable */ interface FieldsInterface extends Serializable { /** * Returns the bytes that comprise the fields */ public function getBytes(): string; } PKZ0ړeeUuid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use BadMethodCallException; use DateTimeInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnsupportedOperationException; use Ramsey\Uuid\Fields\FieldsInterface; use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use ValueError; use function assert; use function bin2hex; use function method_exists; use function preg_match; use function sprintf; use function str_replace; use function strcmp; use function strlen; use function strtolower; use function substr; /** * Uuid provides constants and static methods for working with and generating UUIDs * * @psalm-immutable */ class Uuid implements UuidInterface { use DeprecatedUuidMethodsTrait; /** * When this namespace is specified, the name string is a fully-qualified * domain name * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is a URL * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is an ISO OID * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; /** * When this namespace is specified, the name string is an X.500 DN in DER * or a text output format * * @link http://tools.ietf.org/html/rfc4122#appendix-C RFC 4122, Appendix C: Some Name Space IDs */ public const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; /** * The nil UUID is a special form of UUID that is specified to have all 128 * bits set to zero * * @link http://tools.ietf.org/html/rfc4122#section-4.1.7 RFC 4122, § 4.1.7: Nil UUID */ public const NIL = '00000000-0000-0000-0000-000000000000'; /** * The max UUID is a special form of UUID that is specified to have all 128 * bits set to one * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.10 Max UUID */ public const MAX = 'ffffffff-ffff-ffff-ffff-ffffffffffff'; /** * Variant: reserved, NCS backward compatibility * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RESERVED_NCS = 0; /** * Variant: the UUID layout specified in RFC 4122 * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RFC_4122 = 2; /** * Variant: reserved, Microsoft Corporation backward compatibility * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RESERVED_MICROSOFT = 6; /** * Variant: reserved for future definition * * @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant */ public const RESERVED_FUTURE = 7; /** * @deprecated Use {@see ValidatorInterface::getPattern()} instead. */ public const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$'; /** * Version 1 (Gregorian time) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_TIME = 1; /** * Version 2 (DCE Security) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_DCE_SECURITY = 2; /** * @deprecated Use {@see Uuid::UUID_TYPE_DCE_SECURITY} instead. */ public const UUID_TYPE_IDENTIFIER = 2; /** * Version 3 (name-based and hashed with MD5) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_HASH_MD5 = 3; /** * Version 4 (random) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_RANDOM = 4; /** * Version 5 (name-based and hashed with SHA1) UUID * * @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version */ public const UUID_TYPE_HASH_SHA1 = 5; /** * @deprecated Use {@see Uuid::UUID_TYPE_REORDERED_TIME} instead. */ public const UUID_TYPE_PEABODY = 6; /** * Version 6 (reordered time) UUID * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.6 UUID Version 6 */ public const UUID_TYPE_REORDERED_TIME = 6; /** * Version 7 (Unix Epoch time) UUID * * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.7 UUID Version 7 */ public const UUID_TYPE_UNIX_TIME = 7; /** * @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.8 UUID Version 8 */ public const UUID_TYPE_CUSTOM = 8; /** * DCE Security principal domain * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_PERSON = 0; /** * DCE Security group domain * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_GROUP = 1; /** * DCE Security organization domain * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_ORG = 2; /** * DCE Security domain string names * * @link https://pubs.opengroup.org/onlinepubs/9696989899/chap11.htm#tagcjh_14_05_01_01 DCE 1.1, §11.5.1.1 */ public const DCE_DOMAIN_NAMES = [ self::DCE_DOMAIN_PERSON => 'person', self::DCE_DOMAIN_GROUP => 'group', self::DCE_DOMAIN_ORG => 'org', ]; private static ?UuidFactoryInterface $factory = null; /** * @var bool flag to detect if the UUID factory was replaced internally, * which disables all optimizations for the default/happy path internal * scenarios */ private static bool $factoryReplaced = false; protected CodecInterface $codec; protected NumberConverterInterface $numberConverter; protected Rfc4122FieldsInterface $fields; protected TimeConverterInterface $timeConverter; /** * Creates a universally unique identifier (UUID) from an array of fields * * Unless you're making advanced use of this library to generate identifiers * that deviate from RFC 4122, you probably do not want to instantiate a * UUID directly. Use the static methods, instead: * * ``` * use Ramsey\Uuid\Uuid; * * $timeBasedUuid = Uuid::uuid1(); * $namespaceMd5Uuid = Uuid::uuid3(Uuid::NAMESPACE_URL, 'http://php.net/'); * $randomUuid = Uuid::uuid4(); * $namespaceSha1Uuid = Uuid::uuid5(Uuid::NAMESPACE_URL, 'http://php.net/'); * ``` * * @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID * @param NumberConverterInterface $numberConverter The number converter to use * for converting hex values to/from integers * @param CodecInterface $codec The codec to use when encoding or decoding * UUID strings * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to unix timestamps */ public function __construct( Rfc4122FieldsInterface $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { $this->fields = $fields; $this->codec = $codec; $this->numberConverter = $numberConverter; $this->timeConverter = $timeConverter; } /** * @psalm-return non-empty-string */ public function __toString(): string { return $this->toString(); } /** * Converts the UUID to a string for JSON serialization */ public function jsonSerialize(): string { return $this->toString(); } /** * Converts the UUID to a string for PHP serialization */ public function serialize(): string { return $this->getFields()->getBytes(); } /** * @return array{bytes: string} */ public function __serialize(): array { return ['bytes' => $this->serialize()]; } /** * Re-constructs the object from its serialized form * * @param string $data The serialized PHP string to unserialize into * a UuidInterface instance */ public function unserialize(string $data): void { if (strlen($data) === 16) { /** @var Uuid $uuid */ $uuid = self::getFactory()->fromBytes($data); } else { /** @var Uuid $uuid */ $uuid = self::getFactory()->fromString($data); } $this->codec = $uuid->codec; $this->numberConverter = $uuid->numberConverter; $this->fields = $uuid->fields; $this->timeConverter = $uuid->timeConverter; } /** * @param array{bytes?: string} $data */ public function __unserialize(array $data): void { // @codeCoverageIgnoreStart if (!isset($data['bytes'])) { throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__)); } // @codeCoverageIgnoreEnd $this->unserialize($data['bytes']); } public function compareTo(UuidInterface $other): int { $compare = strcmp($this->toString(), $other->toString()); if ($compare < 0) { return -1; } if ($compare > 0) { return 1; } return 0; } public function equals(?object $other): bool { if (!$other instanceof UuidInterface) { return false; } return $this->compareTo($other) === 0; } /** * @psalm-return non-empty-string */ public function getBytes(): string { return $this->codec->encodeBinary($this); } public function getFields(): FieldsInterface { return $this->fields; } public function getHex(): Hexadecimal { return new Hexadecimal(str_replace('-', '', $this->toString())); } public function getInteger(): IntegerObject { return new IntegerObject($this->numberConverter->fromHex($this->getHex()->toString())); } public function getUrn(): string { return 'urn:uuid:' . $this->toString(); } /** * @psalm-return non-empty-string */ public function toString(): string { return $this->codec->encode($this); } /** * Returns the factory used to create UUIDs */ public static function getFactory(): UuidFactoryInterface { if (self::$factory === null) { self::$factory = new UuidFactory(); } return self::$factory; } /** * Sets the factory used to create UUIDs * * @param UuidFactoryInterface $factory A factory that will be used by this * class to create UUIDs */ public static function setFactory(UuidFactoryInterface $factory): void { // Note: non-strict equality is intentional here. If the factory is configured differently, every assumption // around purity is broken, and we have to internally decide everything differently. // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator self::$factoryReplaced = ($factory != new UuidFactory()); self::$factory = $factory; } /** * Creates a UUID from a byte string * * @param string $bytes A binary string * * @return UuidInterface A UuidInterface instance created from a binary * string representation * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-suppress ImpureStaticProperty we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. */ public static function fromBytes(string $bytes): UuidInterface { if (! self::$factoryReplaced && strlen($bytes) === 16) { $base16Uuid = bin2hex($bytes); // Note: we are calling `fromString` internally because we don't know if the given `$bytes` is a valid UUID return self::fromString( substr($base16Uuid, 0, 8) . '-' . substr($base16Uuid, 8, 4) . '-' . substr($base16Uuid, 12, 4) . '-' . substr($base16Uuid, 16, 4) . '-' . substr($base16Uuid, 20, 12) ); } return self::getFactory()->fromBytes($bytes); } /** * Creates a UUID from the string standard representation * * @param string $uuid A hexadecimal string * * @return UuidInterface A UuidInterface instance created from a hexadecimal * string representation * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-suppress ImpureStaticProperty we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. */ public static function fromString(string $uuid): UuidInterface { $uuid = strtolower($uuid); if (! self::$factoryReplaced && preg_match(LazyUuidFromString::VALID_REGEX, $uuid) === 1) { assert($uuid !== ''); return new LazyUuidFromString($uuid); } return self::getFactory()->fromString($uuid); } /** * Creates a UUID from a DateTimeInterface instance * * @param DateTimeInterface $dateTime The date and time * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID created from a DateTimeInterface instance */ public static function fromDateTime( DateTimeInterface $dateTime, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { return self::getFactory()->fromDateTime($dateTime, $node, $clockSeq); } /** * Creates a UUID from the Hexadecimal object * * @param Hexadecimal $hex Hexadecimal object representing a hexadecimal number * * @return UuidInterface A UuidInterface instance created from the Hexadecimal * object representing a hexadecimal number * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * @psalm-suppress MixedInferredReturnType,MixedReturnStatement */ public static function fromHexadecimal(Hexadecimal $hex): UuidInterface { $factory = self::getFactory(); if (method_exists($factory, 'fromHexadecimal')) { /** * @phpstan-ignore-next-line * @psalm-suppress UndefinedInterfaceMethod */ return self::getFactory()->fromHexadecimal($hex); } throw new BadMethodCallException('The method fromHexadecimal() does not exist on the provided factory'); } /** * Creates a UUID from a 128-bit integer string * * @param string $integer String representation of 128-bit integer * * @return UuidInterface A UuidInterface instance created from the string * representation of a 128-bit integer * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ public static function fromInteger(string $integer): UuidInterface { /** @psalm-suppress ImpureMethodCall */ return self::getFactory()->fromInteger($integer); } /** * Returns true if the provided string is a valid UUID * * @param string $uuid A string to validate as a UUID * * @return bool True if the string is a valid UUID, false otherwise * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-assert-if-true =non-empty-string $uuid */ public static function isValid(string $uuid): bool { /** @psalm-suppress ImpureMethodCall */ return self::getFactory()->getValidator()->validate($uuid); } /** * Returns a version 1 (Gregorian time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return UuidInterface A UuidInterface instance that represents a * version 1 UUID */ public static function uuid1($node = null, ?int $clockSeq = null): UuidInterface { return self::getFactory()->uuid1($node, $clockSeq); } /** * Returns a version 2 (DCE Security) UUID from a local domain, local * identifier, host ID, clock sequence, and the current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes (in a version 2 UUID, the lower 8 bits of this number * are replaced with the domain). * * @return UuidInterface A UuidInterface instance that represents a * version 2 UUID */ public static function uuid2( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { return self::getFactory()->uuid2($localDomain, $localIdentifier, $node, $clockSeq); } /** * Returns a version 3 (name-based) UUID based on the MD5 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 3 UUID * * @psalm-suppress ImpureMethodCall we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners */ public static function uuid3($ns, string $name): UuidInterface { return self::getFactory()->uuid3($ns, $name); } /** * Returns a version 4 (random) UUID * * @return UuidInterface A UuidInterface instance that represents a * version 4 UUID */ public static function uuid4(): UuidInterface { return self::getFactory()->uuid4(); } /** * Returns a version 5 (name-based) UUID based on the SHA-1 hash of a * namespace ID and a name * * @param string|UuidInterface $ns The namespace (must be a valid UUID) * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a * version 5 UUID * * @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants, * but under constant factory setups, this method operates in functionally pure manners * * @psalm-suppress ImpureMethodCall we know that the factory being replaced can lead to massive * havoc across all consumers: that should never happen, and * is generally to be discouraged. Until the factory is kept * un-replaced, this method is effectively pure. */ public static function uuid5($ns, string $name): UuidInterface { return self::getFactory()->uuid5($ns, $name); } /** * Returns a version 6 (reordered time) UUID from a host ID, sequence number, * and the current time * * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates that * could arise when the clock is set backwards in time or if the node ID * changes * * @return UuidInterface A UuidInterface instance that represents a * version 6 UUID */ public static function uuid6( ?Hexadecimal $node = null, ?int $clockSeq = null ): UuidInterface { return self::getFactory()->uuid6($node, $clockSeq); } /** * Returns a version 7 (Unix Epoch time) UUID * * @param DateTimeInterface|null $dateTime An optional date/time from which * to create the version 7 UUID. If not provided, the UUID is generated * using the current date/time. * * @return UuidInterface A UuidInterface instance that represents a * version 7 UUID */ public static function uuid7(?DateTimeInterface $dateTime = null): UuidInterface { $factory = self::getFactory(); if (method_exists($factory, 'uuid7')) { /** @var UuidInterface */ return $factory->uuid7($dateTime); } throw new UnsupportedOperationException( 'The provided factory does not support the uuid7() method', ); } /** * Returns a version 8 (custom) UUID * * The bytes provided may contain any value according to your application's * needs. Be aware, however, that other applications may not understand the * semantics of the value. * * @param string $bytes A 16-byte octet string. This is an open blob * of data that you may fill with 128 bits of information. Be aware, * however, bits 48 through 51 will be replaced with the UUID version * field, and bits 64 and 65 will be replaced with the UUID variant. You * MUST NOT rely on these bits for your application needs. * * @return UuidInterface A UuidInterface instance that represents a * version 8 UUID */ public static function uuid8(string $bytes): UuidInterface { $factory = self::getFactory(); if (method_exists($factory, 'uuid8')) { /** @var UuidInterface */ return $factory->uuid8($bytes); } throw new UnsupportedOperationException( 'The provided factory does not support the uuid8() method', ); } } PKZ. Builder/UuidBuilderInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\UuidInterface; /** * A UUID builder builds instances of UuidInterface * * @psalm-immutable */ interface UuidBuilderInterface { /** * Builds and returns a UuidInterface * * @param CodecInterface $codec The codec to use for building this UuidInterface instance * @param string $bytes The byte string from which to construct a UUID * * @return UuidInterface Implementations may choose to return more specific * instances of UUIDs that implement UuidInterface * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface; } PKZ'UULLBuilder/DegradedUuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\Time\DegradedTimeConverter; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\DegradedUuid; use Ramsey\Uuid\Rfc4122\Fields as Rfc4122Fields; use Ramsey\Uuid\UuidInterface; /** * @deprecated DegradedUuid instances are no longer necessary to support 32-bit * systems. Transition to {@see DefaultUuidBuilder}. * * @psalm-immutable */ class DegradedUuidBuilder implements UuidBuilderInterface { private TimeConverterInterface $timeConverter; /** * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the DegradedUuid * @param TimeConverterInterface|null $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( private NumberConverterInterface $numberConverter, ?TimeConverterInterface $timeConverter = null ) { $this->timeConverter = $timeConverter ?: new DegradedTimeConverter(); } /** * Builds and returns a DegradedUuid * * @param CodecInterface $codec The codec to use for building this DegradedUuid instance * @param string $bytes The byte string from which to construct a UUID * * @return DegradedUuid The DegradedUuidBuild returns an instance of Ramsey\Uuid\DegradedUuid * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { return new DegradedUuid( new Rfc4122Fields($bytes), $this->numberConverter, $codec, $this->timeConverter ); } } PKZSG Builder/BuilderCollection.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Collection\AbstractCollection; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Converter\Time\PhpTimeConverter; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Traversable; /** * A collection of UuidBuilderInterface objects * * @deprecated this class has been deprecated, and will be removed in 5.0.0. The use-case for this class comes from * a pre-`phpstan/phpstan` and pre-`vimeo/psalm` ecosystem, in which type safety had to be mostly enforced * at runtime: that is no longer necessary, now that you can safely verify your code to be correct, and use * more generic types like `iterable` instead. * * @extends AbstractCollection */ class BuilderCollection extends AbstractCollection { public function getType(): string { return UuidBuilderInterface::class; } /** * @psalm-mutation-free * @psalm-suppress ImpureMethodCall * @psalm-suppress InvalidTemplateParam */ public function getIterator(): Traversable { return parent::getIterator(); } /** * Re-constructs the object from its serialized form * * @param string $serialized The serialized PHP string to unserialize into * a UuidInterface instance * * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint * @psalm-suppress RedundantConditionGivenDocblockType */ public function unserialize($serialized): void { /** @var array $data */ $data = unserialize($serialized, [ 'allowed_classes' => [ BrickMathCalculator::class, GenericNumberConverter::class, GenericTimeConverter::class, GuidBuilder::class, NonstandardUuidBuilder::class, PhpTimeConverter::class, Rfc4122UuidBuilder::class, ], ]); $this->data = array_filter( $data, function ($unserialized): bool { return $unserialized instanceof UuidBuilderInterface; } ); } } PKZ'mmBuilder/FallbackBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Exception\BuilderNotFoundException; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\UuidInterface; /** * FallbackBuilder builds a UUID by stepping through a list of UUID builders * until a UUID can be constructed without exceptions * * @psalm-immutable */ class FallbackBuilder implements UuidBuilderInterface { /** * @param iterable $builders An array of UUID builders */ public function __construct(private iterable $builders) { } /** * Builds and returns a UuidInterface instance using the first builder that * succeeds * * @param CodecInterface $codec The codec to use for building this instance * @param string $bytes The byte string from which to construct a UUID * * @return UuidInterface an instance of a UUID object * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { $lastBuilderException = null; foreach ($this->builders as $builder) { try { return $builder->build($codec, $bytes); } catch (UnableToBuildUuidException $exception) { $lastBuilderException = $exception; continue; } } throw new BuilderNotFoundException( 'Could not find a suitable builder for the provided codec and fields', 0, $lastBuilderException ); } } PKZ688Builder/DefaultUuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Builder; use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; /** * @deprecated Transition to {@see Rfc4122UuidBuilder}. * * @psalm-immutable */ class DefaultUuidBuilder extends Rfc4122UuidBuilder { } PKZjGuid/GuidBuilder.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Builder\UuidBuilderInterface; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\UuidInterface; use Throwable; /** * GuidBuilder builds instances of Guid * * @see Guid * * @psalm-immutable */ class GuidBuilder implements UuidBuilderInterface { /** * @param NumberConverterInterface $numberConverter The number converter to * use when constructing the Guid * @param TimeConverterInterface $timeConverter The time converter to use * for converting timestamps extracted from a UUID to Unix timestamps */ public function __construct( private NumberConverterInterface $numberConverter, private TimeConverterInterface $timeConverter ) { } /** * Builds and returns a Guid * * @param CodecInterface $codec The codec to use for building this Guid instance * @param string $bytes The byte string from which to construct a UUID * * @return Guid The GuidBuilder returns an instance of Ramsey\Uuid\Guid\Guid * * @psalm-pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { return new Guid( $this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter ); } catch (Throwable $e) { throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } /** * Proxy method to allow injecting a mock, for testing */ protected function buildFields(string $bytes): Fields { return new Fields($bytes); } } PKZo Guid/Guid.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Uuid; /** * Guid represents a UUID with "native" (little-endian) byte order * * From Wikipedia: * * > The first three fields are unsigned 32- and 16-bit integers and are subject * > to swapping, while the last two fields consist of uninterpreted bytes, not * > subject to swapping. This byte swapping applies even for versions 3, 4, and * > 5, where the canonical fields do not correspond to the content of the UUID. * * The first three fields of a GUID are encoded in little-endian byte order, * while the last three fields are in network (big-endian) byte order. This is * according to the history of the Microsoft definition of a GUID. * * According to the .NET Guid.ToByteArray method documentation: * * > Note that the order of bytes in the returned byte array is different from * > the string representation of a Guid value. The order of the beginning * > four-byte group and the next two two-byte groups is reversed, whereas the * > order of the last two-byte group and the closing six-byte group is the * > same. * * @link https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants UUID Variants on Wikipedia * @link https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid Windows GUID structure * @link https://docs.microsoft.com/en-us/dotnet/api/system.guid .NET Guid Struct * @link https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray .NET Guid.ToByteArray Method * * @psalm-immutable */ final class Guid extends Uuid { public function __construct( Fields $fields, NumberConverterInterface $numberConverter, CodecInterface $codec, TimeConverterInterface $timeConverter ) { parent::__construct($fields, $numberConverter, $codec, $timeConverter); } } PKZ·o[[Guid/Fields.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Guid; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Fields\SerializableFieldsTrait; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\MaxTrait; use Ramsey\Uuid\Rfc4122\NilTrait; use Ramsey\Uuid\Rfc4122\VariantTrait; use Ramsey\Uuid\Rfc4122\VersionTrait; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Uuid; use function bin2hex; use function dechex; use function hexdec; use function pack; use function sprintf; use function str_pad; use function strlen; use function substr; use function unpack; use const STR_PAD_LEFT; /** * GUIDs are comprised of a set of named fields, according to RFC 4122 * * @see Guid * * @psalm-immutable */ final class Fields implements FieldsInterface { use MaxTrait; use NilTrait; use SerializableFieldsTrait; use VariantTrait; use VersionTrait; /** * @param string $bytes A 16-byte binary string representation of a UUID * * @throws InvalidArgumentException if the byte string is not exactly 16 bytes * @throws InvalidArgumentException if the byte string does not represent a GUID * @throws InvalidArgumentException if the byte string does not contain a valid version */ public function __construct(private string $bytes) { if (strlen($this->bytes) !== 16) { throw new InvalidArgumentException( 'The byte string must be 16 bytes long; ' . 'received ' . strlen($this->bytes) . ' bytes' ); } if (!$this->isCorrectVariant()) { throw new InvalidArgumentException( 'The byte string received does not conform to the RFC ' . '4122 or Microsoft Corporation variants' ); } if (!$this->isCorrectVersion()) { throw new InvalidArgumentException( 'The byte string received does not contain a valid version' ); } } public function getBytes(): string { return $this->bytes; } public function getTimeLow(): Hexadecimal { // Swap the bytes from little endian to network byte order. /** @var array $hex */ $hex = unpack( 'H*', pack( 'v*', hexdec(bin2hex(substr($this->bytes, 2, 2))), hexdec(bin2hex(substr($this->bytes, 0, 2))) ) ); return new Hexadecimal((string) ($hex[1] ?? '')); } public function getTimeMid(): Hexadecimal { // Swap the bytes from little endian to network byte order. /** @var array $hex */ $hex = unpack( 'H*', pack( 'v', hexdec(bin2hex(substr($this->bytes, 4, 2))) ) ); return new Hexadecimal((string) ($hex[1] ?? '')); } public function getTimeHiAndVersion(): Hexadecimal { // Swap the bytes from little endian to network byte order. /** @var array $hex */ $hex = unpack( 'H*', pack( 'v', hexdec(bin2hex(substr($this->bytes, 6, 2))) ) ); return new Hexadecimal((string) ($hex[1] ?? '')); } public function getTimestamp(): Hexadecimal { return new Hexadecimal(sprintf( '%03x%04s%08s', hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, $this->getTimeMid()->toString(), $this->getTimeLow()->toString() )); } public function getClockSeq(): Hexadecimal { if ($this->isMax()) { $clockSeq = 0xffff; } elseif ($this->isNil()) { $clockSeq = 0x0000; } else { $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; } return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); } public function getClockSeqHiAndReserved(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); } public function getClockSeqLow(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); } public function getNode(): Hexadecimal { return new Hexadecimal(bin2hex(substr($this->bytes, 10))); } public function getVersion(): ?int { if ($this->isNil() || $this->isMax()) { return null; } /** @var array $parts */ $parts = unpack('n*', $this->bytes); return ((int) $parts[4] >> 4) & 0x00f; } private function isCorrectVariant(): bool { if ($this->isNil() || $this->isMax()) { return true; } $variant = $this->getVariant(); return $variant === Uuid::RFC_4122 || $variant === Uuid::RESERVED_MICROSOFT; } } PKZMŎ--DeprecatedUuidInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use DateTimeInterface; use Ramsey\Uuid\Converter\NumberConverterInterface; /** * This interface encapsulates deprecated methods for ramsey/uuid * * @psalm-immutable */ interface DeprecatedUuidInterface { /** * @deprecated This method will be removed in 5.0.0. There is no alternative * recommendation, so plan accordingly. */ public function getNumberConverter(): NumberConverterInterface; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. * * @return string[] */ public function getFieldsHex(): array; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()}. */ public function getClockSeqHiAndReservedHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()}. */ public function getClockSeqLowHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()}. */ public function getClockSequenceHex(): string; /** * @deprecated In ramsey/uuid version 5.0.0, this will be removed from the * interface. It is available at {@see UuidV1::getDateTime()}. */ public function getDateTime(): DateTimeInterface; /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getLeastSignificantBitsHex(): string; /** * @deprecated This method will be removed in 5.0.0. There is no direct * alternative, but the same information may be obtained by splitting * in half the value returned by {@see UuidInterface::getHex()}. */ public function getMostSignificantBitsHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()}. */ public function getNodeHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()}. */ public function getTimeHiAndVersionHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()}. */ public function getTimeLowHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()}. */ public function getTimeMidHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()}. */ public function getTimestampHex(): string; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}. */ public function getVariant(): ?int; /** * @deprecated Use {@see UuidInterface::getFields()} to get a * {@see FieldsInterface} instance. If it is a * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call * {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}. */ public function getVersion(): ?int; } PKZ+Generator/DceSecurityGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; /** * A DCE Security generator generates strings of binary data based on a local * domain, local identifier, node ID, clock sequence, and the current time * * @see UuidV2 */ interface DceSecurityGeneratorInterface { /** * Generate a binary string from a local domain, local identifier, node ID, * clock sequence, and current time * * @param int $localDomain The local domain to use when generating bytes, * according to DCE Security * @param IntegerObject|null $localIdentifier The local identifier for the * given domain; this may be a UID or GID on POSIX systems, if the local * domain is person or group, or it may be a site-defined identifier * if the local domain is org * @param Hexadecimal|null $node A 48-bit number representing the hardware * address * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return string A binary string */ public function generate( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): string; } PKZq''%Generator/PeclUuidRandomGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use function uuid_create; use function uuid_parse; use const UUID_TYPE_RANDOM; /** * PeclUuidRandomGenerator generates strings of random binary data using ext-uuid * * @link https://pecl.php.net/package/uuid ext-uuid */ class PeclUuidRandomGenerator implements RandomGeneratorInterface { public function generate(int $length): string { $uuid = uuid_create(UUID_TYPE_RANDOM); return uuid_parse($uuid); } } PKZRHH#Generator/PeclUuidNameGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Exception\NameException; use Ramsey\Uuid\UuidInterface; use function sprintf; use function uuid_generate_md5; use function uuid_generate_sha1; use function uuid_parse; /** * PeclUuidNameGenerator generates strings of binary data from a namespace and a * name, using ext-uuid * * @link https://pecl.php.net/package/uuid ext-uuid */ class PeclUuidNameGenerator implements NameGeneratorInterface { /** @psalm-pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string { $uuid = match ($hashAlgorithm) { 'md5' => uuid_generate_md5($ns->toString(), $name), 'sha1' => uuid_generate_sha1($ns->toString(), $name), default => throw new NameException( sprintf( 'Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm ) ), }; return uuid_parse($uuid); } } PKZN"Generator/DefaultNameGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Exception\NameException; use Ramsey\Uuid\UuidInterface; use ValueError; use function hash; /** * DefaultNameGenerator generates strings of binary data based on a namespace, * name, and hashing algorithm */ class DefaultNameGenerator implements NameGeneratorInterface { /** @psalm-pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string { try { /** @var string|bool $bytes */ $bytes = @hash($hashAlgorithm, $ns->getBytes() . $name, true); } catch (ValueError $e) { $bytes = false; // keep same behavior than PHP 7 } if ($bytes === false) { throw new NameException(sprintf( 'Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm )); } return (string) $bytes; } } PKZUGa{ { Generator/CombGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use function bin2hex; use function explode; use function hex2bin; use function microtime; use function str_pad; use function substr; use const STR_PAD_LEFT; /** * CombGenerator generates COMBs (combined UUID/timestamp) * * The CombGenerator, when used with the StringCodec (and, by proxy, the * TimestampLastCombCodec) or the TimestampFirstCombCodec, combines the current * timestamp with a UUID (hence the name "COMB"). The timestamp either appears * as the first or last 48 bits of the COMB, depending on the codec used. * * By default, COMBs will have the timestamp set as the last 48 bits of the * identifier. * * ``` php * $factory = new UuidFactory(); * * $factory->setRandomGenerator(new CombGenerator( * $factory->getRandomGenerator(), * $factory->getNumberConverter() * )); * * $comb = $factory->uuid4(); * ``` * * To generate a COMB with the timestamp as the first 48 bits, set the * TimestampFirstCombCodec as the codec. * * ``` php * $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder())); * ``` * * @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys */ class CombGenerator implements RandomGeneratorInterface { public const TIMESTAMP_BYTES = 6; public function __construct( private RandomGeneratorInterface $generator, private NumberConverterInterface $numberConverter ) { } /** * @throws InvalidArgumentException if $length is not a positive integer * greater than or equal to CombGenerator::TIMESTAMP_BYTES * * @inheritDoc */ public function generate(int $length): string { if ($length < self::TIMESTAMP_BYTES) { throw new InvalidArgumentException( 'Length must be a positive integer greater than or equal to ' . self::TIMESTAMP_BYTES ); } $hash = ''; if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) { $hash = $this->generator->generate($length - self::TIMESTAMP_BYTES); } $lsbTime = str_pad( $this->numberConverter->toHex($this->timestamp()), self::TIMESTAMP_BYTES * 2, '0', STR_PAD_LEFT ); return (string) hex2bin( str_pad( bin2hex($hash), $length - self::TIMESTAMP_BYTES, '0' ) . $lsbTime ); } /** * Returns current timestamp a string integer, precise to 0.00001 seconds */ private function timestamp(): string { $time = explode(' ', microtime(false)); return $time[1] . substr($time[0], 2, 5); } } PKZ"Generator/TimeGeneratorFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\TimeProviderInterface; /** * TimeGeneratorFactory retrieves a default time generator, based on the * environment */ class TimeGeneratorFactory { public function __construct( private NodeProviderInterface $nodeProvider, private TimeConverterInterface $timeConverter, private TimeProviderInterface $timeProvider ) { } /** * Returns a default time generator, based on the current environment */ public function getGenerator(): TimeGeneratorInterface { return new DefaultTimeGenerator( $this->nodeProvider, $this->timeConverter, $this->timeProvider ); } } PKZ!"Generator/NameGeneratorFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; /** * NameGeneratorFactory retrieves a default name generator, based on the * environment */ class NameGeneratorFactory { /** * Returns a default name generator, based on the current environment */ public function getGenerator(): NameGeneratorInterface { return new DefaultNameGenerator(); } } PKZQJGenerator/RandomLibAdapter.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use RandomLib\Factory; use RandomLib\Generator; /** * RandomLibAdapter generates strings of random binary data using the * paragonie/random-lib library * * @deprecated This class will be removed in 5.0.0. Use the default * RandomBytesGenerator or implement your own generator that implements * RandomGeneratorInterface. * * @link https://packagist.org/packages/paragonie/random-lib paragonie/random-lib */ class RandomLibAdapter implements RandomGeneratorInterface { private Generator $generator; /** * Constructs a RandomLibAdapter * * By default, if no Generator is passed in, this creates a high-strength * generator to use when generating random binary data. * * @param Generator|null $generator The generator to use when generating binary data */ public function __construct(?Generator $generator = null) { if ($generator === null) { $factory = new Factory(); $generator = $factory->getHighStrengthGenerator(); } $this->generator = $generator; } public function generate(int $length): string { return $this->generator->generate($length); } } PKZii#Generator/PeclUuidTimeGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use function uuid_create; use function uuid_parse; use const UUID_TYPE_TIME; /** * PeclUuidTimeGenerator generates strings of binary data for time-base UUIDs, * using ext-uuid * * @link https://pecl.php.net/package/uuid ext-uuid */ class PeclUuidTimeGenerator implements TimeGeneratorInterface { /** * @inheritDoc */ public function generate($node = null, ?int $clockSeq = null): string { $uuid = uuid_create(UUID_TYPE_TIME); return uuid_parse($uuid); } } PKZBx$Generator/TimeGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Type\Hexadecimal; /** * A time generator generates strings of binary data based on a node ID, * clock sequence, and the current time */ interface TimeGeneratorInterface { /** * Generate a binary string from a node ID, clock sequence, and current time * * @param Hexadecimal|int|string|null $node A 48-bit number representing the * hardware address; this number may be represented as an integer or a * hexadecimal string * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes * * @return string A binary string */ public function generate($node = null, ?int $clockSeq = null): string; } PKZJ$Generator/RandomGeneratorFactory.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; /** * RandomGeneratorFactory retrieves a default random generator, based on the * environment */ class RandomGeneratorFactory { /** * Returns a default random generator, based on the current environment */ public function getGenerator(): RandomGeneratorInterface { return new RandomBytesGenerator(); } } PKZ'&xx"Generator/RandomBytesGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Exception\RandomSourceException; use Throwable; /** * RandomBytesGenerator generates strings of random binary data using the * built-in `random_bytes()` PHP function * * @link http://php.net/random_bytes random_bytes() */ class RandomBytesGenerator implements RandomGeneratorInterface { /** * @throws RandomSourceException if random_bytes() throws an exception/error * * @inheritDoc */ public function generate(int $length): string { try { return random_bytes($length); } catch (Throwable $exception) { throw new RandomSourceException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } } PKZ m$&Generator/RandomGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; /** * A random generator generates strings of random binary data */ interface RandomGeneratorInterface { /** * Generates a string of randomized binary data * * @param int<1, max> $length The number of bytes of random binary data to generate * * @return string A binary string */ public function generate(int $length): string; } PKZN׈QQGenerator/UnixTimeGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Brick\Math\BigInteger; use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Type\Hexadecimal; use function hash; use function pack; use function str_pad; use function strlen; use function substr; use function substr_replace; use function unpack; use const PHP_INT_SIZE; use const STR_PAD_LEFT; /** * UnixTimeGenerator generates bytes that combine a 48-bit timestamp in * milliseconds since the Unix Epoch with 80 random bits * * Code and concepts within this class are borrowed from the symfony/uid package * and are used under the terms of the MIT license distributed with symfony/uid. * * symfony/uid is copyright (c) Fabien Potencier. * * @link https://symfony.com/components/Uid Symfony Uid component * @link https://github.com/symfony/uid/blob/4f9f537e57261519808a7ce1d941490736522bbc/UuidV7.php Symfony UuidV7 class * @link https://github.com/symfony/uid/blob/6.2/LICENSE MIT License */ class UnixTimeGenerator implements TimeGeneratorInterface { private static string $time = ''; private static ?string $seed = null; private static int $seedIndex = 0; /** @var int[] */ private static array $rand = []; /** @var int[] */ private static array $seedParts; public function __construct( private RandomGeneratorInterface $randomGenerator, private int $intSize = PHP_INT_SIZE ) { } /** * @param Hexadecimal|int|string|null $node Unused in this generator * @param int|null $clockSeq Unused in this generator * @param DateTimeInterface $dateTime A date-time instance to use when * generating bytes * * @inheritDoc */ public function generate($node = null, ?int $clockSeq = null, ?DateTimeInterface $dateTime = null): string { $time = ($dateTime ?? new DateTimeImmutable('now'))->format('Uv'); if ($time > self::$time || ($dateTime !== null && $time !== self::$time)) { $this->randomize($time); } else { $time = $this->increment(); } if ($this->intSize >= 8) { $time = substr(pack('J', (int) $time), -6); } else { $time = str_pad(BigInteger::of($time)->toBytes(false), 6, "\x00", STR_PAD_LEFT); } /** @var non-empty-string */ return $time . pack('n*', self::$rand[1], self::$rand[2], self::$rand[3], self::$rand[4], self::$rand[5]); } private function randomize(string $time): void { if (self::$seed === null) { $seed = $this->randomGenerator->generate(16); self::$seed = $seed; } else { $seed = $this->randomGenerator->generate(10); } /** @var int[] $rand */ $rand = unpack('n*', $seed); $rand[1] &= 0x03ff; self::$rand = $rand; self::$time = $time; } /** * Special thanks to Nicolas Grekas for sharing the following information: * * Within the same ms, we increment the rand part by a random 24-bit number. * * Instead of getting this number from random_bytes(), which is slow, we get * it by sha512-hashing self::$seed. This produces 64 bytes of entropy, * which we need to split in a list of 24-bit numbers. unpack() first splits * them into 16 x 32-bit numbers; we take the first byte of each of these * numbers to get 5 extra 24-bit numbers. Then, we consume those numbers * one-by-one and run this logic every 21 iterations. * * self::$rand holds the random part of the UUID, split into 5 x 16-bit * numbers for x86 portability. We increment this random part by the next * 24-bit number in the self::$seedParts list and decrement * self::$seedIndex. * * @link https://twitter.com/nicolasgrekas/status/1583356938825261061 Tweet from Nicolas Grekas */ private function increment(): string { if (self::$seedIndex === 0 && self::$seed !== null) { self::$seed = hash('sha512', self::$seed, true); /** @var int[] $s */ $s = unpack('l*', self::$seed); $s[] = ($s[1] >> 8 & 0xff0000) | ($s[2] >> 16 & 0xff00) | ($s[3] >> 24 & 0xff); $s[] = ($s[4] >> 8 & 0xff0000) | ($s[5] >> 16 & 0xff00) | ($s[6] >> 24 & 0xff); $s[] = ($s[7] >> 8 & 0xff0000) | ($s[8] >> 16 & 0xff00) | ($s[9] >> 24 & 0xff); $s[] = ($s[10] >> 8 & 0xff0000) | ($s[11] >> 16 & 0xff00) | ($s[12] >> 24 & 0xff); $s[] = ($s[13] >> 8 & 0xff0000) | ($s[14] >> 16 & 0xff00) | ($s[15] >> 24 & 0xff); self::$seedParts = $s; self::$seedIndex = 21; } self::$rand[5] = 0xffff & $carry = self::$rand[5] + (self::$seedParts[self::$seedIndex--] & 0xffffff); self::$rand[4] = 0xffff & $carry = self::$rand[4] + ($carry >> 16); self::$rand[3] = 0xffff & $carry = self::$rand[3] + ($carry >> 16); self::$rand[2] = 0xffff & $carry = self::$rand[2] + ($carry >> 16); self::$rand[1] += $carry >> 16; if (0xfc00 & self::$rand[1]) { $time = self::$time; $mtime = (int) substr($time, -9); if ($this->intSize >= 8 || strlen($time) < 10) { $time = (string) ((int) $time + 1); } elseif ($mtime === 999999999) { $time = (1 + (int) substr($time, 0, -9)) . '000000000'; } else { $mtime++; $time = substr_replace($time, str_pad((string) $mtime, 9, '0', STR_PAD_LEFT), -9); } $this->randomize($time); } return self::$time; } } PKZs**$Generator/NameGeneratorInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\UuidInterface; /** * A name generator generates strings of binary data created by hashing together * a namespace with a name, according to a hashing algorithm */ interface NameGeneratorInterface { /** * Generate a binary string from a namespace and name hashed together with * the specified hashing algorithm * * @param UuidInterface $ns The namespace * @param string $name The name to use for creating a UUID * @param string $hashAlgorithm The hashing algorithm to use * * @return string A binary string * * @psalm-pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string; } PKZh33"Generator/DefaultTimeGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\TimeConverterInterface; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Exception\RandomSourceException; use Ramsey\Uuid\Exception\TimeSourceException; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\TimeProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use Throwable; use function dechex; use function hex2bin; use function is_int; use function pack; use function preg_match; use function sprintf; use function str_pad; use function strlen; use const STR_PAD_LEFT; /** * DefaultTimeGenerator generates strings of binary data based on a node ID, * clock sequence, and the current time */ class DefaultTimeGenerator implements TimeGeneratorInterface { public function __construct( private NodeProviderInterface $nodeProvider, private TimeConverterInterface $timeConverter, private TimeProviderInterface $timeProvider ) { } /** * @throws InvalidArgumentException if the parameters contain invalid values * @throws RandomSourceException if random_int() throws an exception/error * * @inheritDoc */ public function generate($node = null, ?int $clockSeq = null): string { if ($node instanceof Hexadecimal) { $node = $node->toString(); } $node = $this->getValidNode($node); if ($clockSeq === null) { try { // This does not use "stable storage"; see RFC 4122, Section 4.2.1.1. $clockSeq = random_int(0, 0x3fff); } catch (Throwable $exception) { throw new RandomSourceException( $exception->getMessage(), (int) $exception->getCode(), $exception ); } } $time = $this->timeProvider->getTime(); $uuidTime = $this->timeConverter->calculateTime( $time->getSeconds()->toString(), $time->getMicroseconds()->toString() ); $timeHex = str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT); if (strlen($timeHex) !== 16) { throw new TimeSourceException(sprintf( 'The generated time of \'%s\' is larger than expected', $timeHex )); } $timeBytes = (string) hex2bin($timeHex); return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7] . $timeBytes[2] . $timeBytes[3] . $timeBytes[0] . $timeBytes[1] . pack('n*', $clockSeq) . $node; } /** * Uses the node provider given when constructing this instance to get * the node ID (usually a MAC address) * * @param int|string|null $node A node value that may be used to override the node provider * * @return string 6-byte binary string representation of the node * * @throws InvalidArgumentException */ private function getValidNode(int | string | null $node): string { if ($node === null) { $node = $this->nodeProvider->getNode(); } // Convert the node to hex, if it is still an integer. if (is_int($node)) { $node = dechex($node); } if (!preg_match('/^[A-Fa-f0-9]+$/', (string) $node) || strlen((string) $node) > 12) { throw new InvalidArgumentException('Invalid node value'); } return (string) hex2bin(str_pad((string) $node, 12, '0', STR_PAD_LEFT)); } } PKZ(``"Generator/DceSecurityGenerator.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid\Generator; use Ramsey\Uuid\Converter\NumberConverterInterface; use Ramsey\Uuid\Exception\DceSecurityException; use Ramsey\Uuid\Provider\DceSecurityProviderInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Ramsey\Uuid\Uuid; use function hex2bin; use function in_array; use function pack; use function str_pad; use function strlen; use function substr_replace; use const STR_PAD_LEFT; /** * DceSecurityGenerator generates strings of binary data based on a local * domain, local identifier, node ID, clock sequence, and the current time */ class DceSecurityGenerator implements DceSecurityGeneratorInterface { private const DOMAINS = [ Uuid::DCE_DOMAIN_PERSON, Uuid::DCE_DOMAIN_GROUP, Uuid::DCE_DOMAIN_ORG, ]; /** * Upper bounds for the clock sequence in DCE Security UUIDs. */ private const CLOCK_SEQ_HIGH = 63; /** * Lower bounds for the clock sequence in DCE Security UUIDs. */ private const CLOCK_SEQ_LOW = 0; public function __construct( private NumberConverterInterface $numberConverter, private TimeGeneratorInterface $timeGenerator, private DceSecurityProviderInterface $dceSecurityProvider ) { } public function generate( int $localDomain, ?IntegerObject $localIdentifier = null, ?Hexadecimal $node = null, ?int $clockSeq = null ): string { if (!in_array($localDomain, self::DOMAINS)) { throw new DceSecurityException( 'Local domain must be a valid DCE Security domain' ); } if ($localIdentifier && $localIdentifier->isNegative()) { throw new DceSecurityException( 'Local identifier out of bounds; it must be a value between 0 and 4294967295' ); } if ($clockSeq > self::CLOCK_SEQ_HIGH || $clockSeq < self::CLOCK_SEQ_LOW) { throw new DceSecurityException( 'Clock sequence out of bounds; it must be a value between 0 and 63' ); } switch ($localDomain) { case Uuid::DCE_DOMAIN_ORG: if ($localIdentifier === null) { throw new DceSecurityException( 'A local identifier must be provided for the org domain' ); } break; case Uuid::DCE_DOMAIN_PERSON: if ($localIdentifier === null) { $localIdentifier = $this->dceSecurityProvider->getUid(); } break; case Uuid::DCE_DOMAIN_GROUP: default: if ($localIdentifier === null) { $localIdentifier = $this->dceSecurityProvider->getGid(); } break; } $identifierHex = $this->numberConverter->toHex($localIdentifier->toString()); // The maximum value for the local identifier is 0xffffffff, or // 4294967295. This is 8 hexadecimal digits, so if the length of // hexadecimal digits is greater than 8, we know the value is greater // than 0xffffffff. if (strlen($identifierHex) > 8) { throw new DceSecurityException( 'Local identifier out of bounds; it must be a value between 0 and 4294967295' ); } $domainByte = pack('n', $localDomain)[1]; $identifierBytes = (string) hex2bin(str_pad($identifierHex, 8, '0', STR_PAD_LEFT)); if ($node instanceof Hexadecimal) { $node = $node->toString(); } // Shift the clock sequence 8 bits to the left, so it matches 0x3f00. if ($clockSeq !== null) { $clockSeq = $clockSeq << 8; } $bytes = $this->timeGenerator->generate($node, $clockSeq); // Replace bytes in the time-based UUID with DCE Security values. $bytes = substr_replace($bytes, $identifierBytes, 0, 4); return substr_replace($bytes, $domainByte, 9, 1); } } PKZ UuidInterface.phpnu[ * @license http://opensource.org/licenses/MIT MIT */ declare(strict_types=1); namespace Ramsey\Uuid; use JsonSerializable; use Ramsey\Uuid\Fields\FieldsInterface; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Integer as IntegerObject; use Serializable; use Stringable; /** * A UUID is a universally unique identifier adhering to an agreed-upon * representation format and standard for generation * * @psalm-immutable */ interface UuidInterface extends DeprecatedUuidInterface, JsonSerializable, Serializable, Stringable { /** * Returns -1, 0, or 1 if the UUID is less than, equal to, or greater than * the other UUID * * The first of two UUIDs is greater than the second if the most * significant field in which the UUIDs differ is greater for the first * UUID. * * * Q. What's the value of being able to sort UUIDs? * * A. Use them as keys in a B-Tree or similar mapping. * * @param UuidInterface $other The UUID to compare * * @return int -1, 0, or 1 if the UUID is less than, equal to, or greater than $other */ public function compareTo(UuidInterface $other): int; /** * Returns true if the UUID is equal to the provided object * * The result is true if and only if the argument is not null, is a UUID * object, has the same variant, and contains the same value, bit for bit, * as the UUID. * * @param object|null $other An object to test for equality with this UUID * * @return bool True if the other object is equal to this UUID */ public function equals(?object $other): bool; /** * Returns the binary string representation of the UUID * * @psalm-return non-empty-string */ public function getBytes(): string; /** * Returns the fields that comprise this UUID */ public function getFields(): FieldsInterface; /** * Returns the hexadecimal representation of the UUID */ public function getHex(): Hexadecimal; /** * Returns the integer representation of the UUID */ public function getInteger(): IntegerObject; /** * Returns the string standard representation of the UUID as a URN * * @link http://en.wikipedia.org/wiki/Uniform_Resource_Name Uniform Resource Name * @link https://tools.ietf.org/html/rfc4122#section-3 RFC 4122, § 3: Namespace Registration Template */ public function getUrn(): string; /** * Returns the string standard representation of the UUID * * @psalm-return non-empty-string */ public function toString(): string; /** * Casts the UUID to the string standard representation * * @psalm-return non-empty-string */ public function __toString(): string; } PKZ滭r55(Internal/Calculator/NativeCalculator.phpnu[maxDigits = 9; break; case 8: $this->maxDigits = 18; break; default: throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'); } } public function add(string $a, string $b) : string { /** * @psalm-var numeric-string $a * @psalm-var numeric-string $b */ $result = $a + $b; if (is_int($result)) { return (string) $result; } if ($a === '0') { return $b; } if ($b === '0') { return $a; } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig); if ($aNeg) { $result = $this->neg($result); } return $result; } public function sub(string $a, string $b) : string { return $this->add($a, $this->neg($b)); } public function mul(string $a, string $b) : string { /** * @psalm-var numeric-string $a * @psalm-var numeric-string $b */ $result = $a * $b; if (is_int($result)) { return (string) $result; } if ($a === '0' || $b === '0') { return '0'; } if ($a === '1') { return $b; } if ($b === '1') { return $a; } if ($a === '-1') { return $this->neg($b); } if ($b === '-1') { return $this->neg($a); } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $result = $this->doMul($aDig, $bDig); if ($aNeg !== $bNeg) { $result = $this->neg($result); } return $result; } public function divQ(string $a, string $b) : string { return $this->divQR($a, $b)[0]; } public function divR(string $a, string $b): string { return $this->divQR($a, $b)[1]; } public function divQR(string $a, string $b) : array { if ($a === '0') { return ['0', '0']; } if ($a === $b) { return ['1', '0']; } if ($b === '1') { return [$a, '0']; } if ($b === '-1') { return [$this->neg($a), '0']; } /** @psalm-var numeric-string $a */ $na = $a * 1; // cast to number if (is_int($na)) { /** @psalm-var numeric-string $b */ $nb = $b * 1; if (is_int($nb)) { // the only division that may overflow is PHP_INT_MIN / -1, // which cannot happen here as we've already handled a divisor of -1 above. $r = $na % $nb; $q = ($na - $r) / $nb; assert(is_int($q)); return [ (string) $q, (string) $r ]; } } [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); [$q, $r] = $this->doDiv($aDig, $bDig); if ($aNeg !== $bNeg) { $q = $this->neg($q); } if ($aNeg) { $r = $this->neg($r); } return [$q, $r]; } public function pow(string $a, int $e) : string { if ($e === 0) { return '1'; } if ($e === 1) { return $a; } $odd = $e % 2; $e -= $odd; $aa = $this->mul($a, $a); /** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */ $result = $this->pow($aa, $e / 2); if ($odd === 1) { $result = $this->mul($result, $a); } return $result; } /** * Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ */ public function modPow(string $base, string $exp, string $mod) : string { // special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0) if ($base === '0' && $exp === '0' && $mod === '1') { return '0'; } // special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0) if ($exp === '0' && $mod === '1') { return '0'; } $x = $base; $res = '1'; // numbers are positive, so we can use remainder instead of modulo $x = $this->divR($x, $mod); while ($exp !== '0') { if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd $res = $this->divR($this->mul($res, $x), $mod); } $exp = $this->divQ($exp, '2'); $x = $this->divR($this->mul($x, $x), $mod); } return $res; } /** * Adapted from https://cp-algorithms.com/num_methods/roots_newton.html */ public function sqrt(string $n) : string { if ($n === '0') { return '0'; } // initial approximation $x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1); $decreased = false; for (;;) { $nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2'); if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) { break; } $decreased = $this->cmp($nx, $x) < 0; $x = $nx; } return $x; } /** * Performs the addition of two non-signed large integers. */ private function doAdd(string $a, string $b) : string { [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { $blockLength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } /** @psalm-var numeric-string $blockA */ $blockA = \substr($a, $i, $blockLength); /** @psalm-var numeric-string $blockB */ $blockB = \substr($b, $i, $blockLength); $sum = (string) ($blockA + $blockB + $carry); $sumLength = \strlen($sum); if ($sumLength > $blockLength) { $sum = \substr($sum, 1); $carry = 1; } else { if ($sumLength < $blockLength) { $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; } $carry = 0; } $result = $sum . $result; if ($i === 0) { break; } } if ($carry === 1) { $result = '1' . $result; } return $result; } /** * Performs the subtraction of two non-signed large integers. */ private function doSub(string $a, string $b) : string { if ($a === $b) { return '0'; } // Ensure that we always subtract to a positive result: biggest minus smallest. $cmp = $this->doCmp($a, $b); $invert = ($cmp === -1); if ($invert) { $c = $a; $a = $b; $b = $c; } [$a, $b, $length] = $this->pad($a, $b); $carry = 0; $result = ''; $complement = 10 ** $this->maxDigits; for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { $blockLength = $this->maxDigits; if ($i < 0) { $blockLength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } /** @psalm-var numeric-string $blockA */ $blockA = \substr($a, $i, $blockLength); /** @psalm-var numeric-string $blockB */ $blockB = \substr($b, $i, $blockLength); $sum = $blockA - $blockB - $carry; if ($sum < 0) { $sum += $complement; $carry = 1; } else { $carry = 0; } $sum = (string) $sum; $sumLength = \strlen($sum); if ($sumLength < $blockLength) { $sum = \str_repeat('0', $blockLength - $sumLength) . $sum; } $result = $sum . $result; if ($i === 0) { break; } } // Carry cannot be 1 when the loop ends, as a > b assert($carry === 0); $result = \ltrim($result, '0'); if ($invert) { $result = $this->neg($result); } return $result; } /** * Performs the multiplication of two non-signed large integers. */ private function doMul(string $a, string $b) : string { $x = \strlen($a); $y = \strlen($b); $maxDigits = \intdiv($this->maxDigits, 2); $complement = 10 ** $maxDigits; $result = '0'; for ($i = $x - $maxDigits;; $i -= $maxDigits) { $blockALength = $maxDigits; if ($i < 0) { $blockALength += $i; /** @psalm-suppress LoopInvalidation */ $i = 0; } $blockA = (int) \substr($a, $i, $blockALength); $line = ''; $carry = 0; for ($j = $y - $maxDigits;; $j -= $maxDigits) { $blockBLength = $maxDigits; if ($j < 0) { $blockBLength += $j; /** @psalm-suppress LoopInvalidation */ $j = 0; } $blockB = (int) \substr($b, $j, $blockBLength); $mul = $blockA * $blockB + $carry; $value = $mul % $complement; $carry = ($mul - $value) / $complement; $value = (string) $value; $value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT); $line = $value . $line; if ($j === 0) { break; } } if ($carry !== 0) { $line = $carry . $line; } $line = \ltrim($line, '0'); if ($line !== '') { $line .= \str_repeat('0', $x - $blockALength - $i); $result = $this->add($result, $line); } if ($i === 0) { break; } } return $result; } /** * Performs the division of two non-signed large integers. * * @return string[] The quotient and remainder. */ private function doDiv(string $a, string $b) : array { $cmp = $this->doCmp($a, $b); if ($cmp === -1) { return ['0', $a]; } $x = \strlen($a); $y = \strlen($b); // we now know that a >= b && x >= y $q = '0'; // quotient $r = $a; // remainder $z = $y; // focus length, always $y or $y+1 for (;;) { $focus = \substr($a, 0, $z); $cmp = $this->doCmp($focus, $b); if ($cmp === -1) { if ($z === $x) { // remainder < dividend break; } $z++; } $zeros = \str_repeat('0', $x - $z); $q = $this->add($q, '1' . $zeros); $a = $this->sub($a, $b . $zeros); $r = $a; if ($r === '0') { // remainder == 0 break; } $x = \strlen($a); if ($x < $y) { // remainder < dividend break; } $z = $y; } return [$q, $r]; } /** * Compares two non-signed large numbers. * * @return int [-1, 0, 1] */ private function doCmp(string $a, string $b) : int { $x = \strlen($a); $y = \strlen($b); $cmp = $x <=> $y; if ($cmp !== 0) { return $cmp; } return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1] } /** * Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length. * * The numbers must only consist of digits, without leading minus sign. * * @return array{string, string, int} */ private function pad(string $a, string $b) : array { $x = \strlen($a); $y = \strlen($b); if ($x > $y) { $b = \str_repeat('0', $x - $y) . $b; return [$a, $b, $x]; } if ($x < $y) { $a = \str_repeat('0', $y - $x) . $a; return [$a, $b, $y]; } return [$a, $b, $x]; } } PKZmK~L  %Internal/Calculator/GmpCalculator.phpnu[init($a, $b); if ($aNeg && ! $bNeg) { return -1; } if ($bNeg && ! $aNeg) { return 1; } $aLen = \strlen($aDig); $bLen = \strlen($bDig); if ($aLen < $bLen) { $result = -1; } elseif ($aLen > $bLen) { $result = 1; } else { $result = $aDig <=> $bDig; } return $aNeg ? -$result : $result; } /** * Adds two numbers. */ abstract public function add(string $a, string $b) : string; /** * Subtracts two numbers. */ abstract public function sub(string $a, string $b) : string; /** * Multiplies two numbers. */ abstract public function mul(string $a, string $b) : string; /** * Returns the quotient of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string The quotient. */ abstract public function divQ(string $a, string $b) : string; /** * Returns the remainder of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return string The remainder. */ abstract public function divR(string $a, string $b) : string; /** * Returns the quotient and remainder of the division of two numbers. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * * @return array{string, string} An array containing the quotient and remainder. */ abstract public function divQR(string $a, string $b) : array; /** * Exponentiates a number. * * @param string $a The base number. * @param int $e The exponent, validated as an integer between 0 and MAX_POWER. * * @return string The power. */ abstract public function pow(string $a, int $e) : string; /** * @param string $b The modulus; must not be zero. */ public function mod(string $a, string $b) : string { return $this->divR($this->add($this->divR($a, $b), $b), $b); } /** * Returns the modular multiplicative inverse of $x modulo $m. * * If $x has no multiplicative inverse mod m, this method must return null. * * This method can be overridden by the concrete implementation if the underlying library has built-in support. * * @param string $m The modulus; must not be negative or zero. */ public function modInverse(string $x, string $m) : ?string { if ($m === '1') { return '0'; } $modVal = $x; if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) { $modVal = $this->mod($x, $m); } [$g, $x] = $this->gcdExtended($modVal, $m); if ($g !== '1') { return null; } return $this->mod($this->add($this->mod($x, $m), $m), $m); } /** * Raises a number into power with modulo. * * @param string $base The base number; must be positive or zero. * @param string $exp The exponent; must be positive or zero. * @param string $mod The modulus; must be strictly positive. */ abstract public function modPow(string $base, string $exp, string $mod) : string; /** * Returns the greatest common divisor of the two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for GCD calculations. * * @return string The GCD, always positive, or zero if both arguments are zero. */ public function gcd(string $a, string $b) : string { if ($a === '0') { return $this->abs($b); } if ($b === '0') { return $this->abs($a); } return $this->gcd($b, $this->divR($a, $b)); } /** * @return array{string, string, string} GCD, X, Y */ private function gcdExtended(string $a, string $b) : array { if ($a === '0') { return [$b, '0', '1']; } [$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a); $x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); $y = $x1; return [$gcd, $x, $y]; } /** * Returns the square root of the given number, rounded down. * * The result is the largest x such that x² ≤ n. * The input MUST NOT be negative. */ abstract public function sqrt(string $n) : string; /** * Converts a number from an arbitrary base. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for base conversion. * * @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base. * @param int $base The base of the number, validated from 2 to 36. * * @return string The converted number, following the Calculator conventions. */ public function fromBase(string $number, int $base) : string { return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); } /** * Converts a number to an arbitrary base. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for base conversion. * * @param string $number The number to convert, following the Calculator conventions. * @param int $base The base to convert to, validated from 2 to 36. * * @return string The converted number, lowercase. */ public function toBase(string $number, int $base) : string { $negative = ($number[0] === '-'); if ($negative) { $number = \substr($number, 1); } $number = $this->toArbitraryBase($number, self::ALPHABET, $base); if ($negative) { return '-' . $number; } return $number; } /** * Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. * * @param string $number The number to convert, validated as a non-empty string, * containing only chars in the given alphabet/base. * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. * @param int $base The base of the number, validated from 2 to alphabet length. * * @return string The number in base 10, following the Calculator conventions. */ final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string { // remove leading "zeros" $number = \ltrim($number, $alphabet[0]); if ($number === '') { return '0'; } // optimize for "one" if ($number === $alphabet[1]) { return '1'; } $result = '0'; $power = '1'; $base = (string) $base; for ($i = \strlen($number) - 1; $i >= 0; $i--) { $index = \strpos($alphabet, $number[$i]); if ($index !== 0) { $result = $this->add($result, ($index === 1) ? $power : $this->mul($power, (string) $index) ); } if ($i !== 0) { $power = $this->mul($power, $base); } } return $result; } /** * Converts a non-negative number to an arbitrary base using a custom alphabet. * * @param string $number The number to convert, positive or zero, following the Calculator conventions. * @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. * @param int $base The base to convert to, validated from 2 to alphabet length. * * @return string The converted number in the given alphabet. */ final public function toArbitraryBase(string $number, string $alphabet, int $base) : string { if ($number === '0') { return $alphabet[0]; } $base = (string) $base; $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, $base); $remainder = (int) $remainder; $result .= $alphabet[$remainder]; } return \strrev($result); } /** * Performs a rounded division. * * Rounding is performed when the remainder of the division is not zero. * * @param string $a The dividend. * @param string $b The divisor, must not be zero. * @param int $roundingMode The rounding mode. * * @throws \InvalidArgumentException If the rounding mode is invalid. * @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. * * @psalm-suppress ImpureFunctionCall */ final public function divRound(string $a, string $b, int $roundingMode) : string { [$quotient, $remainder] = $this->divQR($a, $b); $hasDiscardedFraction = ($remainder !== '0'); $isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); $discardedFractionSign = function() use ($remainder, $b) : int { $r = $this->abs($this->mul($remainder, '2')); $b = $this->abs($b); return $this->cmp($r, $b); }; $increment = false; switch ($roundingMode) { case RoundingMode::UNNECESSARY: if ($hasDiscardedFraction) { throw RoundingNecessaryException::roundingNecessary(); } break; case RoundingMode::UP: $increment = $hasDiscardedFraction; break; case RoundingMode::DOWN: break; case RoundingMode::CEILING: $increment = $hasDiscardedFraction && $isPositiveOrZero; break; case RoundingMode::FLOOR: $increment = $hasDiscardedFraction && ! $isPositiveOrZero; break; case RoundingMode::HALF_UP: $increment = $discardedFractionSign() >= 0; break; case RoundingMode::HALF_DOWN: $increment = $discardedFractionSign() > 0; break; case RoundingMode::HALF_CEILING: $increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; break; case RoundingMode::HALF_FLOOR: $increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; case RoundingMode::HALF_EVEN: $lastDigit = (int) $quotient[-1]; $lastDigitIsEven = ($lastDigit % 2 === 0); $increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; break; default: throw new \InvalidArgumentException('Invalid rounding mode.'); } if ($increment) { return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); } return $quotient; } /** * Calculates bitwise AND of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. */ public function and(string $a, string $b) : string { return $this->bitwise('and', $a, $b); } /** * Calculates bitwise OR of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. */ public function or(string $a, string $b) : string { return $this->bitwise('or', $a, $b); } /** * Calculates bitwise XOR of two numbers. * * This method can be overridden by the concrete implementation if the underlying library * has built-in support for bitwise operations. */ public function xor(string $a, string $b) : string { return $this->bitwise('xor', $a, $b); } /** * Performs a bitwise operation on a decimal number. * * @param 'and'|'or'|'xor' $operator The operator to use. * @param string $a The left operand. * @param string $b The right operand. */ private function bitwise(string $operator, string $a, string $b) : string { [$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); $aBin = $this->toBinary($aDig); $bBin = $this->toBinary($bDig); $aLen = \strlen($aBin); $bLen = \strlen($bBin); if ($aLen > $bLen) { $bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; } elseif ($bLen > $aLen) { $aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; } if ($aNeg) { $aBin = $this->twosComplement($aBin); } if ($bNeg) { $bBin = $this->twosComplement($bBin); } switch ($operator) { case 'and': $value = $aBin & $bBin; $negative = ($aNeg and $bNeg); break; case 'or': $value = $aBin | $bBin; $negative = ($aNeg or $bNeg); break; case 'xor': $value = $aBin ^ $bBin; $negative = ($aNeg xor $bNeg); break; // @codeCoverageIgnoreStart default: throw new \InvalidArgumentException('Invalid bitwise operator.'); // @codeCoverageIgnoreEnd } if ($negative) { $value = $this->twosComplement($value); } $result = $this->toDecimal($value); return $negative ? $this->neg($result) : $result; } /** * @param string $number A positive, binary number. */ private function twosComplement(string $number) : string { $xor = \str_repeat("\xff", \strlen($number)); $number ^= $xor; for ($i = \strlen($number) - 1; $i >= 0; $i--) { $byte = \ord($number[$i]); if (++$byte !== 256) { $number[$i] = \chr($byte); break; } $number[$i] = "\x00"; if ($i === 0) { $number = "\x01" . $number; } } return $number; } /** * Converts a decimal number to a binary string. * * @param string $number The number to convert, positive or zero, only digits. */ private function toBinary(string $number) : string { $result = ''; while ($number !== '0') { [$number, $remainder] = $this->divQR($number, '256'); $result .= \chr((int) $remainder); } return \strrev($result); } /** * Returns the positive decimal representation of a binary number. * * @param string $bytes The bytes representing the number. */ private function toDecimal(string $bytes) : string { $result = '0'; $power = '1'; for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { $index = \ord($bytes[$i]); if ($index !== 0) { $result = $this->add($result, ($index === 1) ? $power : $this->mul($power, (string) $index) ); } if ($i !== 0) { $power = $this->mul($power, '256'); } } return $result; } } PKZ nEWEWBigDecimal.phpnu[value = $value; $this->scale = $scale; } /** * Creates a BigDecimal of the given value. * * @throws MathException If the value cannot be converted to a BigDecimal. * * @psalm-pure */ public static function of(BigNumber|int|float|string $value) : BigDecimal { return parent::of($value)->toBigDecimal(); } /** * Creates a BigDecimal from an unscaled value and a scale. * * Example: `(12345, 3)` will result in the BigDecimal `12.345`. * * @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger. * @param int $scale The scale of the number, positive or zero. * * @throws \InvalidArgumentException If the scale is negative. * * @psalm-pure */ public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('The scale cannot be negative.'); } return new BigDecimal((string) BigInteger::of($value), $scale); } /** * Returns a BigDecimal representing zero, with a scale of zero. * * @psalm-pure */ public static function zero() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $zero */ static $zero; if ($zero === null) { $zero = new BigDecimal('0'); } return $zero; } /** * Returns a BigDecimal representing one, with a scale of zero. * * @psalm-pure */ public static function one() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $one */ static $one; if ($one === null) { $one = new BigDecimal('1'); } return $one; } /** * Returns a BigDecimal representing ten, with a scale of zero. * * @psalm-pure */ public static function ten() : BigDecimal { /** * @psalm-suppress ImpureStaticVariable * @var BigDecimal|null $ten */ static $ten; if ($ten === null) { $ten = new BigDecimal('10'); } return $ten; } /** * Returns the sum of this number and the given one. * * The result has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal. * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ public function plus(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0' && $that->scale <= $this->scale) { return $this; } if ($this->value === '0' && $this->scale <= $that->scale) { return $that; } [$a, $b] = $this->scaleValues($this, $that); $value = Calculator::get()->add($a, $b); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($value, $scale); } /** * Returns the difference of this number and the given one. * * The result has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal. * * @throws MathException If the number is not valid, or is not convertible to a BigDecimal. */ public function minus(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0' && $that->scale <= $this->scale) { return $this; } [$a, $b] = $this->scaleValues($this, $that); $value = Calculator::get()->sub($a, $b); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($value, $scale); } /** * Returns the product of this number and the given one. * * The result has a scale of `$this->scale + $that->scale`. * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal. * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal. */ public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '1' && $that->scale === 0) { return $this; } if ($this->value === '1' && $this->scale === 0) { return $that; } $value = Calculator::get()->mul($this->value, $that->value); $scale = $this->scale + $that->scale; return new BigDecimal($value, $scale); } /** * Returns the result of the division of this number by the given one, at the given scale. * * @param BigNumber|int|float|string $that The divisor. * @param int|null $scale The desired scale, or null to use the scale of this number. * @param int $roundingMode An optional rounding mode. * * @throws \InvalidArgumentException If the scale or rounding mode is invalid. * @throws MathException If the number is invalid, is zero, or rounding was necessary. */ public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } if ($scale === null) { $scale = $this->scale; } elseif ($scale < 0) { throw new \InvalidArgumentException('Scale cannot be negative.'); } if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) { return $this; } $p = $this->valueWithMinScale($that->scale + $scale); $q = $that->valueWithMinScale($this->scale - $scale); $result = Calculator::get()->divRound($p, $q, $roundingMode); return new BigDecimal($result, $scale); } /** * Returns the exact result of the division of this number by the given one. * * The scale of the result is automatically calculated to fit all the fraction digits. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero, * or the result yields an infinite number of digits. */ public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } [, $b] = $this->scaleValues($this, $that); $d = \rtrim($b, '0'); $scale = \strlen($b) - \strlen($d); $calculator = Calculator::get(); foreach ([5, 2] as $prime) { for (;;) { $lastDigit = (int) $d[-1]; if ($lastDigit % $prime !== 0) { break; } $d = $calculator->divQ($d, (string) $prime); $scale++; } } return $this->dividedBy($that, $scale)->stripTrailingZeros(); } /** * Returns this number exponentiated to the given value. * * The result has a scale of `$this->scale * $exponent`. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigDecimal { if ($exponent === 0) { return BigDecimal::one(); } if ($exponent === 1) { return $this; } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { throw new \InvalidArgumentException(\sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, Calculator::MAX_POWER )); } return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent); } /** * Returns the quotient of the division of this number by this given one. * * The quotient has a scale of `0`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function quotient(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); $quotient = Calculator::get()->divQ($p, $q); return new BigDecimal($quotient, 0); } /** * Returns the remainder of the division of this number by this given one. * * The remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function remainder(BigNumber|int|float|string $that) : BigDecimal { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); $remainder = Calculator::get()->divR($p, $q); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; return new BigDecimal($remainder, $scale); } /** * Returns the quotient and remainder of the division of this number by the given one. * * The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. * * @return BigDecimal[] An array containing the quotient and the remainder. * * @throws MathException If the divisor is not a valid decimal number, or is zero. */ public function quotientAndRemainder(BigNumber|int|float|string $that) : array { $that = BigDecimal::of($that); if ($that->isZero()) { throw DivisionByZeroException::divisionByZero(); } $p = $this->valueWithMinScale($that->scale); $q = $that->valueWithMinScale($this->scale); [$quotient, $remainder] = Calculator::get()->divQR($p, $q); $scale = $this->scale > $that->scale ? $this->scale : $that->scale; $quotient = new BigDecimal($quotient, 0); $remainder = new BigDecimal($remainder, $scale); return [$quotient, $remainder]; } /** * Returns the square root of this number, rounded down to the given number of decimals. * * @throws \InvalidArgumentException If the scale is negative. * @throws NegativeNumberException If this number is negative. */ public function sqrt(int $scale) : BigDecimal { if ($scale < 0) { throw new \InvalidArgumentException('Scale cannot be negative.'); } if ($this->value === '0') { return new BigDecimal('0', $scale); } if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); } $value = $this->value; $addDigits = 2 * $scale - $this->scale; if ($addDigits > 0) { // add zeros $value .= \str_repeat('0', $addDigits); } elseif ($addDigits < 0) { // trim digits if (-$addDigits >= \strlen($this->value)) { // requesting a scale too low, will always yield a zero result return new BigDecimal('0', $scale); } $value = \substr($value, 0, $addDigits); } $value = Calculator::get()->sqrt($value); return new BigDecimal($value, $scale); } /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the left. */ public function withPointMovedLeft(int $n) : BigDecimal { if ($n === 0) { return $this; } if ($n < 0) { return $this->withPointMovedRight(-$n); } return new BigDecimal($this->value, $this->scale + $n); } /** * Returns a copy of this BigDecimal with the decimal point moved $n places to the right. */ public function withPointMovedRight(int $n) : BigDecimal { if ($n === 0) { return $this; } if ($n < 0) { return $this->withPointMovedLeft(-$n); } $value = $this->value; $scale = $this->scale - $n; if ($scale < 0) { if ($value !== '0') { $value .= \str_repeat('0', -$scale); } $scale = 0; } return new BigDecimal($value, $scale); } /** * Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part. */ public function stripTrailingZeros() : BigDecimal { if ($this->scale === 0) { return $this; } $trimmedValue = \rtrim($this->value, '0'); if ($trimmedValue === '') { return BigDecimal::zero(); } $trimmableZeros = \strlen($this->value) - \strlen($trimmedValue); if ($trimmableZeros === 0) { return $this; } if ($trimmableZeros > $this->scale) { $trimmableZeros = $this->scale; } $value = \substr($this->value, 0, -$trimmableZeros); $scale = $this->scale - $trimmableZeros; return new BigDecimal($value, $scale); } /** * Returns the absolute value of this number. */ public function abs() : BigDecimal { return $this->isNegative() ? $this->negated() : $this; } /** * Returns the negated value of this number. */ public function negated() : BigDecimal { return new BigDecimal(Calculator::get()->neg($this->value), $this->scale); } public function compareTo(BigNumber|int|float|string $that) : int { $that = BigNumber::of($that); if ($that instanceof BigInteger) { $that = $that->toBigDecimal(); } if ($that instanceof BigDecimal) { [$a, $b] = $this->scaleValues($this, $that); return Calculator::get()->cmp($a, $b); } return - $that->compareTo($this); } public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } public function getUnscaledValue() : BigInteger { return self::newBigInteger($this->value); } public function getScale() : int { return $this->scale; } /** * Returns a string representing the integral part of this decimal number. * * Example: `-123.456` => `-123`. */ public function getIntegralPart() : string { if ($this->scale === 0) { return $this->value; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, 0, -$this->scale); } /** * Returns a string representing the fractional part of this decimal number. * * If the scale is zero, an empty string is returned. * * Examples: `-123.456` => '456', `123` => ''. */ public function getFractionalPart() : string { if ($this->scale === 0) { return ''; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, -$this->scale); } /** * Returns whether this decimal number has a non-zero fractional part. */ public function hasNonZeroFractionalPart() : bool { return $this->getFractionalPart() !== \str_repeat('0', $this->scale); } public function toBigInteger() : BigInteger { $zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); return self::newBigInteger($zeroScaleDecimal->value); } public function toBigDecimal() : BigDecimal { return $this; } public function toBigRational() : BigRational { $numerator = self::newBigInteger($this->value); $denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale)); return self::newBigRational($numerator, $denominator, false); } public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { if ($scale === $this->scale) { return $this; } return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode); } public function toInt() : int { return $this->toBigInteger()->toInt(); } public function toFloat() : float { return (float) (string) $this; } public function __toString() : string { if ($this->scale === 0) { return $this->value; } $value = $this->getUnscaledValueWithLeadingZeros(); return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale); } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{value: string, scale: int} */ public function __serialize(): array { return ['value' => $this->value, 'scale' => $this->scale]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{value: string, scale: int} $data * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->value)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->value = $data['value']; $this->scale = $data['scale']; } /** * This method is required by interface Serializable and SHOULD NOT be accessed directly. * * @internal */ public function serialize() : string { return $this->value . ':' . $this->scale; } /** * This method is only here to implement interface Serializable and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @throws \LogicException */ public function unserialize($value) : void { if (isset($this->value)) { throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); } [$value, $scale] = \explode(':', $value); $this->value = $value; $this->scale = (int) $scale; } /** * Puts the internal values of the given decimal numbers on the same scale. * * @return array{string, string} The scaled integer values of $x and $y. */ private function scaleValues(BigDecimal $x, BigDecimal $y) : array { $a = $x->value; $b = $y->value; if ($b !== '0' && $x->scale > $y->scale) { $b .= \str_repeat('0', $x->scale - $y->scale); } elseif ($a !== '0' && $x->scale < $y->scale) { $a .= \str_repeat('0', $y->scale - $x->scale); } return [$a, $b]; } private function valueWithMinScale(int $scale) : string { $value = $this->value; if ($this->value !== '0' && $scale > $this->scale) { $value .= \str_repeat('0', $scale - $this->scale); } return $value; } /** * Adds leading zeros if necessary to the unscaled value to represent the full decimal number. */ private function getUnscaledValueWithLeadingZeros() : string { $value = $this->value; $targetLength = $this->scale + 1; $negative = ($value[0] === '-'); $length = \strlen($value); if ($negative) { $length--; } if ($length >= $targetLength) { return $this->value; } if ($negative) { $value = \substr($value, 1); } $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT); if ($negative) { $value = '-' . $value; } return $value; } } PKZ<< BigNumber.phpnu[[\-\+])?' . '(?:' . '(?:' . '(?[0-9]+)?' . '(?\.)?' . '(?[0-9]+)?' . '(?:[eE](?[\-\+]?[0-9]+))?' . ')|(?:' . '(?[0-9]+)' . '\/?' . '(?[0-9]+)' . ')' . ')' . '$/'; /** * Creates a BigNumber of the given value. * * The concrete return type is dependent on the given value, with the following rules: * * - BigNumber instances are returned as is * - integer numbers are returned as BigInteger * - floating point numbers are converted to a string then parsed as such * - strings containing a `/` character are returned as BigRational * - strings containing a `.` character or using an exponential notation are returned as BigDecimal * - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger * * @throws NumberFormatException If the format of the number is not valid. * @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. * * @psalm-pure */ public static function of(BigNumber|int|float|string $value) : BigNumber { if ($value instanceof BigNumber) { return $value; } if (\is_int($value)) { return new BigInteger((string) $value); } $value = \is_float($value) ? self::floatToString($value) : $value; $throw = static function() use ($value) : void { throw new NumberFormatException(\sprintf( 'The given value "%s" does not represent a valid number.', $value )); }; if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) { $throw(); } $getMatch = static fn(string $value): ?string => (($matches[$value] ?? '') !== '') ? $matches[$value] : null; $sign = $getMatch('sign'); $numerator = $getMatch('numerator'); $denominator = $getMatch('denominator'); if ($numerator !== null) { assert($denominator !== null); if ($sign !== null) { $numerator = $sign . $numerator; } $numerator = self::cleanUp($numerator); $denominator = self::cleanUp($denominator); if ($denominator === '0') { throw DivisionByZeroException::denominatorMustNotBeZero(); } return new BigRational( new BigInteger($numerator), new BigInteger($denominator), false ); } $point = $getMatch('point'); $integral = $getMatch('integral'); $fractional = $getMatch('fractional'); $exponent = $getMatch('exponent'); if ($integral === null && $fractional === null) { $throw(); } if ($integral === null) { $integral = '0'; } if ($point !== null || $exponent !== null) { $fractional = ($fractional ?? ''); $exponent = ($exponent !== null) ? (int) $exponent : 0; if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { throw new NumberFormatException('Exponent too large.'); } $unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional); $scale = \strlen($fractional) - $exponent; if ($scale < 0) { if ($unscaledValue !== '0') { $unscaledValue .= \str_repeat('0', - $scale); } $scale = 0; } return new BigDecimal($unscaledValue, $scale); } $integral = self::cleanUp(($sign ?? '') . $integral); return new BigInteger($integral); } /** * Safely converts float to string, avoiding locale-dependent issues. * * @see https://github.com/brick/math/pull/20 * * @psalm-pure * @psalm-suppress ImpureFunctionCall */ private static function floatToString(float $float) : string { $currentLocale = \setlocale(LC_NUMERIC, '0'); \setlocale(LC_NUMERIC, 'C'); $result = (string) $float; \setlocale(LC_NUMERIC, $currentLocale); return $result; } /** * Proxy method to access BigInteger's protected constructor from sibling classes. * * @internal * @psalm-pure */ protected function newBigInteger(string $value) : BigInteger { return new BigInteger($value); } /** * Proxy method to access BigDecimal's protected constructor from sibling classes. * * @internal * @psalm-pure */ protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal { return new BigDecimal($value, $scale); } /** * Proxy method to access BigRational's protected constructor from sibling classes. * * @internal * @psalm-pure */ protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational { return new BigRational($numerator, $denominator, $checkDenominator); } /** * Returns the minimum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-suppress LessSpecificReturnStatement * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ public static function min(BigNumber|int|float|string ...$values) : static { $min = null; foreach ($values as $value) { $value = static::of($value); if ($min === null || $value->isLessThan($min)) { $min = $value; } } if ($min === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $min; } /** * Returns the maximum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible * to an instance of the class this method is called on. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-suppress LessSpecificReturnStatement * @psalm-suppress MoreSpecificReturnType * @psalm-pure */ public static function max(BigNumber|int|float|string ...$values) : static { $max = null; foreach ($values as $value) { $value = static::of($value); if ($max === null || $value->isGreaterThan($max)) { $max = $value; } } if ($max === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $max; } /** * Returns the sum of the given values. * * @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible * to an instance of the class this method is called on. * * @throws \InvalidArgumentException If no values are given. * @throws MathException If an argument is not valid. * * @psalm-pure */ public static function sum(BigNumber|int|float|string ...$values) : static { /** @var static|null $sum */ $sum = null; foreach ($values as $value) { $value = static::of($value); $sum = $sum === null ? $value : self::add($sum, $value); } if ($sum === null) { throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); } return $sum; } /** * Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. * * @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to * concrete classes the responsibility to perform the addition themselves or delegate it to the given number, * depending on their ability to perform the operation. This will also require a version bump because we're * potentially breaking custom BigNumber implementations (if any...) * * @psalm-pure */ private static function add(BigNumber $a, BigNumber $b) : BigNumber { if ($a instanceof BigRational) { return $a->plus($b); } if ($b instanceof BigRational) { return $b->plus($a); } if ($a instanceof BigDecimal) { return $a->plus($b); } if ($b instanceof BigDecimal) { return $b->plus($a); } /** @var BigInteger $a */ return $a->plus($b); } /** * Removes optional leading zeros and + sign from the given number. * * @param string $number The number, validated as a non-empty string of digits with optional leading sign. * * @psalm-pure */ private static function cleanUp(string $number) : string { $firstChar = $number[0]; if ($firstChar === '+' || $firstChar === '-') { $number = \substr($number, 1); } $number = \ltrim($number, '0'); if ($number === '') { return '0'; } if ($firstChar === '-') { return '-' . $number; } return $number; } /** * Checks if this number is equal to the given one. */ public function isEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) === 0; } /** * Checks if this number is strictly lower than the given one. */ public function isLessThan(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) < 0; } /** * Checks if this number is lower than or equal to the given one. */ public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) <= 0; } /** * Checks if this number is strictly greater than the given one. */ public function isGreaterThan(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) > 0; } /** * Checks if this number is greater than or equal to the given one. */ public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool { return $this->compareTo($that) >= 0; } /** * Checks if this number equals zero. */ public function isZero() : bool { return $this->getSign() === 0; } /** * Checks if this number is strictly negative. */ public function isNegative() : bool { return $this->getSign() < 0; } /** * Checks if this number is negative or zero. */ public function isNegativeOrZero() : bool { return $this->getSign() <= 0; } /** * Checks if this number is strictly positive. */ public function isPositive() : bool { return $this->getSign() > 0; } /** * Checks if this number is positive or zero. */ public function isPositiveOrZero() : bool { return $this->getSign() >= 0; } /** * Returns the sign of this number. * * @return int -1 if the number is negative, 0 if zero, 1 if positive. */ abstract public function getSign() : int; /** * Compares this number to the given one. * * @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`. * * @throws MathException If the number is not valid. */ abstract public function compareTo(BigNumber|int|float|string $that) : int; /** * Converts this number to a BigInteger. * * @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. */ abstract public function toBigInteger() : BigInteger; /** * Converts this number to a BigDecimal. * * @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. */ abstract public function toBigDecimal() : BigDecimal; /** * Converts this number to a BigRational. */ abstract public function toBigRational() : BigRational; /** * Converts this number to a BigDecimal with the given scale, using rounding if necessary. * * @param int $scale The scale of the resulting `BigDecimal`. * @param int $roundingMode A `RoundingMode` constant. * * @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. * This only applies when RoundingMode::UNNECESSARY is used. */ abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; /** * Returns the exact value of this number as a native integer. * * If this number cannot be converted to a native integer without losing precision, an exception is thrown. * Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. * * @throws MathException If this number cannot be exactly converted to a native integer. */ abstract public function toInt() : int; /** * Returns an approximation of this number as a floating-point value. * * Note that this method can discard information as the precision of a floating-point value * is inherently limited. * * If the number is greater than the largest representable floating point number, positive infinity is returned. * If the number is less than the smallest representable floating point number, negative infinity is returned. */ abstract public function toFloat() : float; /** * Returns a string representation of this number. * * The output of this method can be parsed by the `of()` factory method; * this will yield an object equal to this one, without any information loss. */ abstract public function __toString() : string; public function jsonSerialize() : string { return $this->__toString(); } } PKZE 2 2BigRational.phpnu[isZero()) { throw DivisionByZeroException::denominatorMustNotBeZero(); } if ($denominator->isNegative()) { $numerator = $numerator->negated(); $denominator = $denominator->negated(); } } $this->numerator = $numerator; $this->denominator = $denominator; } /** * Creates a BigRational of the given value. * * @throws MathException If the value cannot be converted to a BigRational. * * @psalm-pure */ public static function of(BigNumber|int|float|string $value) : BigRational { return parent::of($value)->toBigRational(); } /** * Creates a BigRational out of a numerator and a denominator. * * If the denominator is negative, the signs of both the numerator and the denominator * will be inverted to ensure that the denominator is always positive. * * @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger. * * @throws NumberFormatException If an argument does not represent a valid number. * @throws RoundingNecessaryException If an argument represents a non-integer number. * @throws DivisionByZeroException If the denominator is zero. * * @psalm-pure */ public static function nd( BigNumber|int|float|string $numerator, BigNumber|int|float|string $denominator, ) : BigRational { $numerator = BigInteger::of($numerator); $denominator = BigInteger::of($denominator); return new BigRational($numerator, $denominator, true); } /** * Returns a BigRational representing zero. * * @psalm-pure */ public static function zero() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $zero */ static $zero; if ($zero === null) { $zero = new BigRational(BigInteger::zero(), BigInteger::one(), false); } return $zero; } /** * Returns a BigRational representing one. * * @psalm-pure */ public static function one() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $one */ static $one; if ($one === null) { $one = new BigRational(BigInteger::one(), BigInteger::one(), false); } return $one; } /** * Returns a BigRational representing ten. * * @psalm-pure */ public static function ten() : BigRational { /** * @psalm-suppress ImpureStaticVariable * @var BigRational|null $ten */ static $ten; if ($ten === null) { $ten = new BigRational(BigInteger::ten(), BigInteger::one(), false); } return $ten; } public function getNumerator() : BigInteger { return $this->numerator; } public function getDenominator() : BigInteger { return $this->denominator; } /** * Returns the quotient of the division of the numerator by the denominator. */ public function quotient() : BigInteger { return $this->numerator->quotient($this->denominator); } /** * Returns the remainder of the division of the numerator by the denominator. */ public function remainder() : BigInteger { return $this->numerator->remainder($this->denominator); } /** * Returns the quotient and remainder of the division of the numerator by the denominator. * * @return BigInteger[] */ public function quotientAndRemainder() : array { return $this->numerator->quotientAndRemainder($this->denominator); } /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. * * @throws MathException If the number is not valid. */ public function plus(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the difference of this number and the given one. * * @param BigNumber|int|float|string $that The number to subtract. * * @throws MathException If the number is not valid. */ public function minus(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the product of this number and the given one. * * @param BigNumber|int|float|string $that The multiplier. * * @throws MathException If the multiplier is not a valid number. */ public function multipliedBy(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->numerator); $denominator = $this->denominator->multipliedBy($that->denominator); return new BigRational($numerator, $denominator, false); } /** * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. * * @throws MathException If the divisor is not a valid number, or is zero. */ public function dividedBy(BigNumber|int|float|string $that) : BigRational { $that = BigRational::of($that); $numerator = $this->numerator->multipliedBy($that->denominator); $denominator = $this->denominator->multipliedBy($that->numerator); return new BigRational($numerator, $denominator, true); } /** * Returns this number exponentiated to the given value. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigRational { if ($exponent === 0) { $one = BigInteger::one(); return new BigRational($one, $one, false); } if ($exponent === 1) { return $this; } return new BigRational( $this->numerator->power($exponent), $this->denominator->power($exponent), false ); } /** * Returns the reciprocal of this BigRational. * * The reciprocal has the numerator and denominator swapped. * * @throws DivisionByZeroException If the numerator is zero. */ public function reciprocal() : BigRational { return new BigRational($this->denominator, $this->numerator, true); } /** * Returns the absolute value of this BigRational. */ public function abs() : BigRational { return new BigRational($this->numerator->abs(), $this->denominator, false); } /** * Returns the negated value of this BigRational. */ public function negated() : BigRational { return new BigRational($this->numerator->negated(), $this->denominator, false); } /** * Returns the simplified value of this BigRational. */ public function simplified() : BigRational { $gcd = $this->numerator->gcd($this->denominator); $numerator = $this->numerator->quotient($gcd); $denominator = $this->denominator->quotient($gcd); return new BigRational($numerator, $denominator, false); } public function compareTo(BigNumber|int|float|string $that) : int { return $this->minus($that)->getSign(); } public function getSign() : int { return $this->numerator->getSign(); } public function toBigInteger() : BigInteger { $simplified = $this->simplified(); if (! $simplified->denominator->isEqualTo(1)) { throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.'); } return $simplified->numerator; } public function toBigDecimal() : BigDecimal { return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); } public function toBigRational() : BigRational { return $this; } public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); } public function toInt() : int { return $this->toBigInteger()->toInt(); } public function toFloat() : float { $simplified = $this->simplified(); return $simplified->numerator->toFloat() / $simplified->denominator->toFloat(); } public function __toString() : string { $numerator = (string) $this->numerator; $denominator = (string) $this->denominator; if ($denominator === '1') { return $numerator; } return $this->numerator . '/' . $this->denominator; } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{numerator: BigInteger, denominator: BigInteger} */ public function __serialize(): array { return ['numerator' => $this->numerator, 'denominator' => $this->denominator]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{numerator: BigInteger, denominator: BigInteger} $data * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->numerator)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->numerator = $data['numerator']; $this->denominator = $data['denominator']; } /** * This method is required by interface Serializable and SHOULD NOT be accessed directly. * * @internal */ public function serialize() : string { return $this->numerator . '/' . $this->denominator; } /** * This method is only here to implement interface Serializable and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @throws \LogicException */ public function unserialize($value) : void { if (isset($this->numerator)) { throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); } [$numerator, $denominator] = \explode('/', $value); $this->numerator = BigInteger::of($numerator); $this->denominator = BigInteger::of($denominator); } } PKZV~&Exception/IntegerOverflowException.phpnu[(Exception/RoundingNecessaryException.phpnu[ 126) { $char = \strtoupper(\dechex($ord)); if ($ord < 10) { $char = '0' . $char; } } else { $char = '"' . $char . '"'; } return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char)); } } PKZəb%Exception/DivisionByZeroException.phpnu[value = $value; } /** * Creates a BigInteger of the given value. * * @throws MathException If the value cannot be converted to a BigInteger. * * @psalm-pure */ public static function of(BigNumber|int|float|string $value) : BigInteger { return parent::of($value)->toBigInteger(); } /** * Creates a number from a string in a given base. * * The string can optionally be prefixed with the `+` or `-` sign. * * Bases greater than 36 are not supported by this method, as there is no clear consensus on which of the lowercase * or uppercase characters should come first. Instead, this method accepts any base up to 36, and does not * differentiate lowercase and uppercase characters, which are considered equal. * * For bases greater than 36, and/or custom alphabets, use the fromArbitraryBase() method. * * @param string $number The number to convert, in the given base. * @param int $base The base of the number, between 2 and 36. * * @throws NumberFormatException If the number is empty, or contains invalid chars for the given base. * @throws \InvalidArgumentException If the base is out of range. * * @psalm-pure */ public static function fromBase(string $number, int $base) : BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } if ($base < 2 || $base > 36) { throw new \InvalidArgumentException(\sprintf('Base %d is not in range 2 to 36.', $base)); } if ($number[0] === '-') { $sign = '-'; $number = \substr($number, 1); } elseif ($number[0] === '+') { $sign = ''; $number = \substr($number, 1); } else { $sign = ''; } if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } $number = \ltrim($number, '0'); if ($number === '') { // The result will be the same in any base, avoid further calculation. return BigInteger::zero(); } if ($number === '1') { // The result will be the same in any base, avoid further calculation. return new BigInteger($sign . '1'); } $pattern = '/[^' . \substr(Calculator::ALPHABET, 0, $base) . ']/'; if (\preg_match($pattern, \strtolower($number), $matches) === 1) { throw new NumberFormatException(\sprintf('"%s" is not a valid character in base %d.', $matches[0], $base)); } if ($base === 10) { // The number is usable as is, avoid further calculation. return new BigInteger($sign . $number); } $result = Calculator::get()->fromBase($number, $base); return new BigInteger($sign . $result); } /** * Parses a string containing an integer in an arbitrary base, using a custom alphabet. * * Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers. * * @param string $number The number to parse. * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * * @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet. * @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars. * * @psalm-pure */ public static function fromArbitraryBase(string $number, string $alphabet) : BigInteger { if ($number === '') { throw new NumberFormatException('The number cannot be empty.'); } $base = \strlen($alphabet); if ($base < 2) { throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); } $pattern = '/[^' . \preg_quote($alphabet, '/') . ']/'; if (\preg_match($pattern, $number, $matches) === 1) { throw NumberFormatException::charNotInAlphabet($matches[0]); } $number = Calculator::get()->fromArbitraryBase($number, $alphabet, $base); return new BigInteger($number); } /** * Translates a string of bytes containing the binary representation of a BigInteger into a BigInteger. * * The input string is assumed to be in big-endian byte-order: the most significant byte is in the zeroth element. * * If `$signed` is true, the input is assumed to be in two's-complement representation, and the leading bit is * interpreted as a sign bit. If `$signed` is false, the input is interpreted as an unsigned number, and the * resulting BigInteger will always be positive or zero. * * This method can be used to retrieve a number exported by `toBytes()`, as long as the `$signed` flags match. * * @param string $value The byte string. * @param bool $signed Whether to interpret as a signed number in two's-complement representation with a leading * sign bit. * * @throws NumberFormatException If the string is empty. */ public static function fromBytes(string $value, bool $signed = true) : BigInteger { if ($value === '') { throw new NumberFormatException('The byte string must not be empty.'); } $twosComplement = false; if ($signed) { $x = \ord($value[0]); if (($twosComplement = ($x >= 0x80))) { $value = ~$value; } } $number = self::fromBase(\bin2hex($value), 16); if ($twosComplement) { return $number->plus(1)->negated(); } return $number; } /** * Generates a pseudo-random number in the range 0 to 2^numBits - 1. * * Using the default random bytes generator, this method is suitable for cryptographic use. * * @psalm-param (callable(int): string)|null $randomBytesGenerator * * @param int $numBits The number of bits. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, and returns a * string of random bytes of the given length. Defaults to the * `random_bytes()` function. * * @throws \InvalidArgumentException If $numBits is negative. */ public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null) : BigInteger { if ($numBits < 0) { throw new \InvalidArgumentException('The number of bits cannot be negative.'); } if ($numBits === 0) { return BigInteger::zero(); } if ($randomBytesGenerator === null) { $randomBytesGenerator = 'random_bytes'; } $byteLength = \intdiv($numBits - 1, 8) + 1; $extraBits = ($byteLength * 8 - $numBits); $bitmask = \chr(0xFF >> $extraBits); $randomBytes = $randomBytesGenerator($byteLength); $randomBytes[0] = $randomBytes[0] & $bitmask; return self::fromBytes($randomBytes, false); } /** * Generates a pseudo-random number between `$min` and `$max`. * * Using the default random bytes generator, this method is suitable for cryptographic use. * * @psalm-param (callable(int): string)|null $randomBytesGenerator * * @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger. * @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger. * @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, * and returns a string of random bytes of the given length. * Defaults to the `random_bytes()` function. * * @throws MathException If one of the parameters cannot be converted to a BigInteger, * or `$min` is greater than `$max`. */ public static function randomRange( BigNumber|int|float|string $min, BigNumber|int|float|string $max, ?callable $randomBytesGenerator = null ) : BigInteger { $min = BigInteger::of($min); $max = BigInteger::of($max); if ($min->isGreaterThan($max)) { throw new MathException('$min cannot be greater than $max.'); } if ($min->isEqualTo($max)) { return $min; } $diff = $max->minus($min); $bitLength = $diff->getBitLength(); // try until the number is in range (50% to 100% chance of success) do { $randomNumber = self::randomBits($bitLength, $randomBytesGenerator); } while ($randomNumber->isGreaterThan($diff)); return $randomNumber->plus($min); } /** * Returns a BigInteger representing zero. * * @psalm-pure */ public static function zero() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $zero */ static $zero; if ($zero === null) { $zero = new BigInteger('0'); } return $zero; } /** * Returns a BigInteger representing one. * * @psalm-pure */ public static function one() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $one */ static $one; if ($one === null) { $one = new BigInteger('1'); } return $one; } /** * Returns a BigInteger representing ten. * * @psalm-pure */ public static function ten() : BigInteger { /** * @psalm-suppress ImpureStaticVariable * @var BigInteger|null $ten */ static $ten; if ($ten === null) { $ten = new BigInteger('10'); } return $ten; } public static function gcdMultiple(BigInteger $a, BigInteger ...$n): BigInteger { $result = $a; foreach ($n as $next) { $result = $result->gcd($next); if ($result->isEqualTo(1)) { return $result; } } return $result; } /** * Returns the sum of this number and the given one. * * @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigInteger. * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ public function plus(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { return $this; } if ($this->value === '0') { return $that; } $value = Calculator::get()->add($this->value, $that->value); return new BigInteger($value); } /** * Returns the difference of this number and the given one. * * @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigInteger. * * @throws MathException If the number is not valid, or is not convertible to a BigInteger. */ public function minus(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { return $this; } $value = Calculator::get()->sub($this->value, $that->value); return new BigInteger($value); } /** * Returns the product of this number and the given one. * * @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger. * * @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger. */ public function multipliedBy(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($this->value === '1') { return $that; } $value = Calculator::get()->mul($this->value, $that->value); return new BigInteger($value); } /** * Returns the result of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * @param int $roundingMode An optional rounding mode. * * @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero, * or RoundingMode::UNNECESSARY is used and the remainder is not zero. */ public function dividedBy(BigNumber|int|float|string $that, int $roundingMode = RoundingMode::UNNECESSARY) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $result = Calculator::get()->divRound($this->value, $that->value, $roundingMode); return new BigInteger($result); } /** * Returns this number exponentiated to the given value. * * @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. */ public function power(int $exponent) : BigInteger { if ($exponent === 0) { return BigInteger::one(); } if ($exponent === 1) { return $this; } if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { throw new \InvalidArgumentException(\sprintf( 'The exponent %d is not in the range 0 to %d.', $exponent, Calculator::MAX_POWER )); } return new BigInteger(Calculator::get()->pow($this->value, $exponent)); } /** * Returns the quotient of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @throws DivisionByZeroException If the divisor is zero. */ public function quotient(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return $this; } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $quotient = Calculator::get()->divQ($this->value, $that->value); return new BigInteger($quotient); } /** * Returns the remainder of the division of this number by the given one. * * The remainder, when non-zero, has the same sign as the dividend. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @throws DivisionByZeroException If the divisor is zero. */ public function remainder(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '1') { return BigInteger::zero(); } if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } $remainder = Calculator::get()->divR($this->value, $that->value); return new BigInteger($remainder); } /** * Returns the quotient and remainder of the division of this number by the given one. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @return BigInteger[] An array containing the quotient and the remainder. * * @throws DivisionByZeroException If the divisor is zero. */ public function quotientAndRemainder(BigNumber|int|float|string $that) : array { $that = BigInteger::of($that); if ($that->value === '0') { throw DivisionByZeroException::divisionByZero(); } [$quotient, $remainder] = Calculator::get()->divQR($this->value, $that->value); return [ new BigInteger($quotient), new BigInteger($remainder) ]; } /** * Returns the modulo of this number and the given one. * * The modulo operation yields the same result as the remainder operation when both operands are of the same sign, * and may differ when signs are different. * * The result of the modulo operation, when non-zero, has the same sign as the divisor. * * @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger. * * @throws DivisionByZeroException If the divisor is zero. */ public function mod(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); } $value = Calculator::get()->mod($this->value, $that->value); return new BigInteger($value); } /** * Returns the modular multiplicative inverse of this BigInteger modulo $m. * * @throws DivisionByZeroException If $m is zero. * @throws NegativeNumberException If $m is negative. * @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger * is not relatively prime to m). */ public function modInverse(BigInteger $m) : BigInteger { if ($m->value === '0') { throw DivisionByZeroException::modulusMustNotBeZero(); } if ($m->isNegative()) { throw new NegativeNumberException('Modulus must not be negative.'); } if ($m->value === '1') { return BigInteger::zero(); } $value = Calculator::get()->modInverse($this->value, $m->value); if ($value === null) { throw new MathException('Unable to compute the modInverse for the given modulus.'); } return new BigInteger($value); } /** * Returns this number raised into power with modulo. * * This operation only works on positive numbers. * * @param BigNumber|int|float|string $exp The exponent. Must be positive or zero. * @param BigNumber|int|float|string $mod The modulus. Must be strictly positive. * * @throws NegativeNumberException If any of the operands is negative. * @throws DivisionByZeroException If the modulus is zero. */ public function modPow(BigNumber|int|float|string $exp, BigNumber|int|float|string $mod) : BigInteger { $exp = BigInteger::of($exp); $mod = BigInteger::of($mod); if ($this->isNegative() || $exp->isNegative() || $mod->isNegative()) { throw new NegativeNumberException('The operands cannot be negative.'); } if ($mod->isZero()) { throw DivisionByZeroException::modulusMustNotBeZero(); } $result = Calculator::get()->modPow($this->value, $exp->value, $mod->value); return new BigInteger($result); } /** * Returns the greatest common divisor of this number and the given one. * * The GCD is always positive, unless both operands are zero, in which case it is zero. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function gcd(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); if ($that->value === '0' && $this->value[0] !== '-') { return $this; } if ($this->value === '0' && $that->value[0] !== '-') { return $that; } $value = Calculator::get()->gcd($this->value, $that->value); return new BigInteger($value); } /** * Returns the integer square root number of this number, rounded down. * * The result is the largest x such that x² ≤ n. * * @throws NegativeNumberException If this number is negative. */ public function sqrt() : BigInteger { if ($this->value[0] === '-') { throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); } $value = Calculator::get()->sqrt($this->value); return new BigInteger($value); } /** * Returns the absolute value of this number. */ public function abs() : BigInteger { return $this->isNegative() ? $this->negated() : $this; } /** * Returns the inverse of this number. */ public function negated() : BigInteger { return new BigInteger(Calculator::get()->neg($this->value)); } /** * Returns the integer bitwise-and combined with another integer. * * This method returns a negative BigInteger if and only if both operands are negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function and(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->and($this->value, $that->value)); } /** * Returns the integer bitwise-or combined with another integer. * * This method returns a negative BigInteger if and only if either of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function or(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->or($this->value, $that->value)); } /** * Returns the integer bitwise-xor combined with another integer. * * This method returns a negative BigInteger if and only if exactly one of the operands is negative. * * @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number. */ public function xor(BigNumber|int|float|string $that) : BigInteger { $that = BigInteger::of($that); return new BigInteger(Calculator::get()->xor($this->value, $that->value)); } /** * Returns the bitwise-not of this BigInteger. */ public function not() : BigInteger { return $this->negated()->minus(1); } /** * Returns the integer left shifted by a given number of bits. */ public function shiftedLeft(int $distance) : BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { return $this->shiftedRight(- $distance); } return $this->multipliedBy(BigInteger::of(2)->power($distance)); } /** * Returns the integer right shifted by a given number of bits. */ public function shiftedRight(int $distance) : BigInteger { if ($distance === 0) { return $this; } if ($distance < 0) { return $this->shiftedLeft(- $distance); } $operand = BigInteger::of(2)->power($distance); if ($this->isPositiveOrZero()) { return $this->quotient($operand); } return $this->dividedBy($operand, RoundingMode::UP); } /** * Returns the number of bits in the minimal two's-complement representation of this BigInteger, excluding a sign bit. * * For positive BigIntegers, this is equivalent to the number of bits in the ordinary binary representation. * Computes (ceil(log2(this < 0 ? -this : this+1))). */ public function getBitLength() : int { if ($this->value === '0') { return 0; } if ($this->isNegative()) { return $this->abs()->minus(1)->getBitLength(); } return \strlen($this->toBase(2)); } /** * Returns the index of the rightmost (lowest-order) one bit in this BigInteger. * * Returns -1 if this BigInteger contains no one bits. */ public function getLowestSetBit() : int { $n = $this; $bitLength = $this->getBitLength(); for ($i = 0; $i <= $bitLength; $i++) { if ($n->isOdd()) { return $i; } $n = $n->shiftedRight(1); } return -1; } /** * Returns whether this number is even. */ public function isEven() : bool { return \in_array($this->value[-1], ['0', '2', '4', '6', '8'], true); } /** * Returns whether this number is odd. */ public function isOdd() : bool { return \in_array($this->value[-1], ['1', '3', '5', '7', '9'], true); } /** * Returns true if and only if the designated bit is set. * * Computes ((this & (1<shiftedRight($n)->isOdd(); } public function compareTo(BigNumber|int|float|string $that) : int { $that = BigNumber::of($that); if ($that instanceof BigInteger) { return Calculator::get()->cmp($this->value, $that->value); } return - $that->compareTo($this); } public function getSign() : int { return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); } public function toBigInteger() : BigInteger { return $this; } public function toBigDecimal() : BigDecimal { return self::newBigDecimal($this->value); } public function toBigRational() : BigRational { return self::newBigRational($this, BigInteger::one(), false); } public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal { return $this->toBigDecimal()->toScale($scale, $roundingMode); } public function toInt() : int { $intValue = (int) $this->value; if ($this->value !== (string) $intValue) { throw IntegerOverflowException::toIntOverflow($this); } return $intValue; } public function toFloat() : float { return (float) $this->value; } /** * Returns a string representation of this number in the given base. * * The output will always be lowercase for bases greater than 10. * * @throws \InvalidArgumentException If the base is out of range. */ public function toBase(int $base) : string { if ($base === 10) { return $this->value; } if ($base < 2 || $base > 36) { throw new \InvalidArgumentException(\sprintf('Base %d is out of range [2, 36]', $base)); } return Calculator::get()->toBase($this->value, $base); } /** * Returns a string representation of this number in an arbitrary base with a custom alphabet. * * Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers; * a NegativeNumberException will be thrown when attempting to call this method on a negative number. * * @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8. * * @throws NegativeNumberException If this number is negative. * @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars. */ public function toArbitraryBase(string $alphabet) : string { $base = \strlen($alphabet); if ($base < 2) { throw new \InvalidArgumentException('The alphabet must contain at least 2 chars.'); } if ($this->value[0] === '-') { throw new NegativeNumberException(__FUNCTION__ . '() does not support negative numbers.'); } return Calculator::get()->toArbitraryBase($this->value, $alphabet, $base); } /** * Returns a string of bytes containing the binary representation of this BigInteger. * * The string is in big-endian byte-order: the most significant byte is in the zeroth element. * * If `$signed` is true, the output will be in two's-complement representation, and a sign bit will be prepended to * the output. If `$signed` is false, no sign bit will be prepended, and this method will throw an exception if the * number is negative. * * The string will contain the minimum number of bytes required to represent this BigInteger, including a sign bit * if `$signed` is true. * * This representation is compatible with the `fromBytes()` factory method, as long as the `$signed` flags match. * * @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit. * * @throws NegativeNumberException If $signed is false, and the number is negative. */ public function toBytes(bool $signed = true) : string { if (! $signed && $this->isNegative()) { throw new NegativeNumberException('Cannot convert a negative number to a byte string when $signed is false.'); } $hex = $this->abs()->toBase(16); if (\strlen($hex) % 2 !== 0) { $hex = '0' . $hex; } $baseHexLength = \strlen($hex); if ($signed) { if ($this->isNegative()) { $bin = \hex2bin($hex); assert($bin !== false); $hex = \bin2hex(~$bin); $hex = self::fromBase($hex, 16)->plus(1)->toBase(16); $hexLength = \strlen($hex); if ($hexLength < $baseHexLength) { $hex = \str_repeat('0', $baseHexLength - $hexLength) . $hex; } if ($hex[0] < '8') { $hex = 'FF' . $hex; } } else { if ($hex[0] >= '8') { $hex = '00' . $hex; } } } return \hex2bin($hex); } public function __toString() : string { return $this->value; } /** * This method is required for serializing the object and SHOULD NOT be accessed directly. * * @internal * * @return array{value: string} */ public function __serialize(): array { return ['value' => $this->value]; } /** * This method is only here to allow unserializing the object and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @param array{value: string} $data * * @throws \LogicException */ public function __unserialize(array $data): void { if (isset($this->value)) { throw new \LogicException('__unserialize() is an internal function, it must not be called directly.'); } $this->value = $data['value']; } /** * This method is required by interface Serializable and SHOULD NOT be accessed directly. * * @internal */ public function serialize() : string { return $this->value; } /** * This method is only here to implement interface Serializable and cannot be accessed directly. * * @internal * @psalm-suppress RedundantPropertyInitializationCheck * * @throws \LogicException */ public function unserialize($value) : void { if (isset($this->value)) { throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); } $this->value = $value; } } PKZSRoundingMode.phpnu[= 0.5; otherwise, behaves as for DOWN. * Note that this is the rounding mode commonly taught at school. */ public const HALF_UP = 5; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. * * Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN. */ public const HALF_DOWN = 6; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity. * * If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN. */ public const HALF_CEILING = 7; /** * Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity. * * If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP. */ public const HALF_FLOOR = 8; /** * Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor. * * Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd; * behaves as for HALF_DOWN if it's even. * * Note that this is the rounding mode that statistically minimizes * cumulative error when applied repeatedly over a sequence of calculations. * It is sometimes known as "Banker's rounding", and is chiefly used in the USA. */ public const HALF_EVEN = 9; } PKZ,YYIterators/Mapper.phpnu[callback = $callback; } public function current(): mixed { return ($this->callback)(parent::current(), parent::key()); } } PKZ2 Iterators/CachingIterator.phpnu[getIterator(); } while ($iterator instanceof \IteratorAggregate); assert($iterator instanceof \Iterator); } elseif ($iterator instanceof \Iterator) { } elseif ($iterator instanceof \Traversable) { $iterator = new \IteratorIterator($iterator); } else { throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? $iterator::class : gettype($iterator))); } parent::__construct($iterator, 0); } /** * Is the current element the first one? */ public function isFirst(?int $gridWidth = null): bool { return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0); } /** * Is the current element the last one? */ public function isLast(?int $gridWidth = null): bool { return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0); } /** * Is the iterator empty? */ public function isEmpty(): bool { return $this->counter === 0; } /** * Is the counter odd? */ public function isOdd(): bool { return $this->counter % 2 === 1; } /** * Is the counter even? */ public function isEven(): bool { return $this->counter % 2 === 0; } /** * Returns the counter. */ public function getCounter(): int { return $this->counter; } /** * Returns the count of elements. */ public function count(): int { $inner = $this->getInnerIterator(); if ($inner instanceof \Countable) { return $inner->count(); } else { throw new Nette\NotSupportedException('Iterator is not countable.'); } } /** * Forwards to the next element. */ public function next(): void { parent::next(); if (parent::valid()) { $this->counter++; } } /** * Rewinds the Iterator. */ public function rewind(): void { parent::rewind(); $this->counter = parent::valid() ? 1 : 0; } /** * Returns the next key. */ public function getNextKey(): mixed { return $this->getInnerIterator()->key(); } /** * Returns the next element. */ public function getNextValue(): mixed { return $this->getInnerIterator()->current(); } } PKZATranslator.phpnu[$name ?? null; if (is_iterable($handlers)) { foreach ($handlers as $handler) { $handler(...$args); } } elseif ($handlers !== null) { throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.'); } return null; } ObjectHelpers::strictCall($class, $name); } /** * @throws MemberAccessException */ public static function __callStatic(string $name, array $args) { ObjectHelpers::strictStaticCall(static::class, $name); } /** * @return mixed * @throws MemberAccessException if the property is not defined. */ public function &__get(string $name) { $class = static::class; if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter if (!($prop & 0b0001)) { throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); } $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name); if ($prop & 0b10000) { $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() $loc = isset($trace['file'], $trace['line']) ? " in $trace[file] on line $trace[line]" : ''; trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); } if ($prop & 0b0100) { // return by reference return $this->$m(); } else { $val = $this->$m(); return $val; } } else { ObjectHelpers::strictGet($class, $name); } } /** * @throws MemberAccessException if the property is not defined or is read-only */ public function __set(string $name, mixed $value): void { $class = static::class; if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property $this->$name = $value; } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter if (!($prop & 0b1000)) { throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); } $m = 'set' . ucfirst($name); if ($prop & 0b10000) { $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() $loc = isset($trace['file'], $trace['line']) ? " in $trace[file] on line $trace[line]" : ''; trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); } $this->$m($value); } else { ObjectHelpers::strictSet($class, $name); } } /** * @throws MemberAccessException */ public function __unset(string $name): void { $class = static::class; if (!ObjectHelpers::hasProperty($class, $name)) { throw new MemberAccessException("Cannot unset the property $class::\$$name."); } } public function __isset(string $name): bool { return isset(ObjectHelpers::getMagicProperties(static::class)[$name]); } } PKZrkcompatibility.phpnu[ $b it returns 1 * @throws \LogicException if one of parameters is NAN */ public static function compare(float $a, float $b): int { if (is_nan($a) || is_nan($b)) { throw new \LogicException('Trying to compare NAN'); } elseif (!is_finite($a) && !is_finite($b) && $a === $b) { return 0; } $diff = abs($a - $b); if (($diff < self::Epsilon || ($diff / max(abs($a), abs($b)) < self::Epsilon))) { return 0; } return $a < $b ? -1 : 1; } /** * Returns true if $a = $b * @throws \LogicException if one of parameters is NAN */ public static function areEqual(float $a, float $b): bool { return self::compare($a, $b) === 0; } /** * Returns true if $a < $b * @throws \LogicException if one of parameters is NAN */ public static function isLessThan(float $a, float $b): bool { return self::compare($a, $b) < 0; } /** * Returns true if $a <= $b * @throws \LogicException if one of parameters is NAN */ public static function isLessThanOrEqualTo(float $a, float $b): bool { return self::compare($a, $b) <= 0; } /** * Returns true if $a > $b * @throws \LogicException if one of parameters is NAN */ public static function isGreaterThan(float $a, float $b): bool { return self::compare($a, $b) > 0; } /** * Returns true if $a >= $b * @throws \LogicException if one of parameters is NAN */ public static function isGreaterThanOrEqualTo(float $a, float $b): bool { return self::compare($a, $b) >= 0; } } PKZUUUtils/Image.phpnu[ * $image = Image::fromFile('nette.jpg'); * $image->resize(150, 100); * $image->sharpen(); * $image->send(); * * * @method Image affine(array $affine, array $clip = null) * @method array affineMatrixConcat(array $m1, array $m2) * @method array affineMatrixGet(int $type, mixed $options = null) * @method void alphaBlending(bool $on) * @method void antialias(bool $on) * @method void arc($x, $y, $w, $h, $start, $end, $color) * @method void char(int $font, $x, $y, string $char, $color) * @method void charUp(int $font, $x, $y, string $char, $color) * @method int colorAllocate($red, $green, $blue) * @method int colorAllocateAlpha($red, $green, $blue, $alpha) * @method int colorAt($x, $y) * @method int colorClosest($red, $green, $blue) * @method int colorClosestAlpha($red, $green, $blue, $alpha) * @method int colorClosestHWB($red, $green, $blue) * @method void colorDeallocate($color) * @method int colorExact($red, $green, $blue) * @method int colorExactAlpha($red, $green, $blue, $alpha) * @method void colorMatch(Image $image2) * @method int colorResolve($red, $green, $blue) * @method int colorResolveAlpha($red, $green, $blue, $alpha) * @method void colorSet($index, $red, $green, $blue) * @method array colorsForIndex($index) * @method int colorsTotal() * @method int colorTransparent($color = null) * @method void convolution(array $matrix, float $div, float $offset) * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH) * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1) * @method void ellipse($cx, $cy, $w, $h, $color) * @method void fill($x, $y, $color) * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style) * @method void filledEllipse($cx, $cy, $w, $h, $color) * @method void filledPolygon(array $points, $numPoints, $color) * @method void filledRectangle($x1, $y1, $x2, $y2, $color) * @method void fillToBorder($x, $y, $border, $color) * @method void filter($filtertype) * @method void flip(int $mode) * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null) * @method void gammaCorrect(float $inputgamma, float $outputgamma) * @method array getClip() * @method int interlace($interlace = null) * @method bool isTrueColor() * @method void layerEffect($effect) * @method void line($x1, $y1, $x2, $y2, $color) * @method void openPolygon(array $points, int $num_points, int $color) * @method void paletteCopy(Image $source) * @method void paletteToTrueColor() * @method void polygon(array $points, $numPoints, $color) * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null) * @method void rectangle($x1, $y1, $x2, $y2, $col) * @method mixed resolution(int $res_x = null, int $res_y = null) * @method Image rotate(float $angle, $backgroundColor) * @method void saveAlpha(bool $saveflag) * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) * @method void setBrush(Image $brush) * @method void setClip(int $x1, int $y1, int $x2, int $y2) * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED) * @method void setPixel($x, $y, $color) * @method void setStyle(array $style) * @method void setThickness($thickness) * @method void setTile(Image $tile) * @method void string($font, $x, $y, string $s, $col) * @method void stringUp($font, $x, $y, string $s, $col) * @method void trueColorToPalette(bool $dither, $ncolors) * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text) * @property-read int $width * @property-read int $height * @property-read \GdImage $imageResource */ class Image { use Nette\SmartObject; /** Prevent from getting resized to a bigger size than the original */ public const ShrinkOnly = 0b0001; /** Resizes to a specified width and height without keeping aspect ratio */ public const Stretch = 0b0010; /** Resizes to fit into a specified width and height and preserves aspect ratio */ public const OrSmaller = 0b0000; /** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */ public const OrBigger = 0b0100; /** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */ public const Cover = 0b1000; /** @deprecated use Image::ShrinkOnly */ public const SHRINK_ONLY = self::ShrinkOnly; /** @deprecated use Image::Stretch */ public const STRETCH = self::Stretch; /** @deprecated use Image::OrSmaller */ public const FIT = self::OrSmaller; /** @deprecated use Image::OrBigger */ public const FILL = self::OrBigger; /** @deprecated use Image::Cover */ public const EXACT = self::Cover; /** @deprecated use Image::EmptyGIF */ public const EMPTY_GIF = self::EmptyGIF; /** image types */ public const JPEG = IMAGETYPE_JPEG, PNG = IMAGETYPE_PNG, GIF = IMAGETYPE_GIF, WEBP = IMAGETYPE_WEBP, AVIF = 19, // IMAGETYPE_AVIF, BMP = IMAGETYPE_BMP; public const EmptyGIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"; private const Formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::AVIF => 'avif', self::BMP => 'bmp']; private \GdImage $image; /** * Returns RGB color (0..255) and transparency (0..127). */ public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array { return [ 'red' => max(0, min(255, $red)), 'green' => max(0, min(255, $green)), 'blue' => max(0, min(255, $blue)), 'alpha' => max(0, min(127, $transparency)), ]; } /** * Reads an image from a file and returns its type in $type. * @throws Nette\NotSupportedException if gd extension is not loaded * @throws UnknownImageFileException if file not found or file type is not known */ public static function fromFile(string $file, ?int &$type = null): static { if (!extension_loaded('gd')) { throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); } $type = self::detectTypeFromFile($file); if (!$type) { throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); } return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__); } /** * Reads an image from a string and returns its type in $type. * @throws Nette\NotSupportedException if gd extension is not loaded * @throws ImageException */ public static function fromString(string $s, ?int &$type = null): static { if (!extension_loaded('gd')) { throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); } $type = self::detectTypeFromString($s); if (!$type) { throw new UnknownImageFileException('Unknown type of image.'); } return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__); } private static function invokeSafe(string $func, string $arg, string $message, string $callee): static { $errors = []; $res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void { $errors[] = $message; }); if (!$res) { throw new ImageException($message . ' Errors: ' . implode(', ', $errors)); } elseif ($errors) { trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING); } return new static($res); } /** * Creates a new true color image of the given dimensions. The default color is black. * @throws Nette\NotSupportedException if gd extension is not loaded */ public static function fromBlank(int $width, int $height, ?array $color = null): static { if (!extension_loaded('gd')) { throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); } if ($width < 1 || $height < 1) { throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.'); } $image = imagecreatetruecolor($width, $height); if ($color) { $color += ['alpha' => 0]; $color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']); imagealphablending($image, false); imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color); imagealphablending($image, true); } return new static($image); } /** * Returns the type of image from file. */ public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int { [$width, $height, $type] = @getimagesize($file); // @ - files smaller than 12 bytes causes read error return isset(self::Formats[$type]) ? $type : null; } /** * Returns the type of image from string. */ public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int { [$width, $height, $type] = @getimagesizefromstring($s); // @ - strings smaller than 12 bytes causes read error return isset(self::Formats[$type]) ? $type : null; } /** * Returns the file extension for the given `Image::XXX` constant. */ public static function typeToExtension(int $type): string { if (!isset(self::Formats[$type])) { throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); } return self::Formats[$type]; } /** * Returns the `Image::XXX` constant for given file extension. */ public static function extensionToType(string $extension): int { $extensions = array_flip(self::Formats) + ['jpg' => self::JPEG]; $extension = strtolower($extension); if (!isset($extensions[$extension])) { throw new Nette\InvalidArgumentException("Unsupported file extension '$extension'."); } return $extensions[$extension]; } /** * Returns the mime type for the given `Image::XXX` constant. */ public static function typeToMimeType(int $type): string { return 'image/' . self::typeToExtension($type); } /** * Wraps GD image. */ public function __construct(\GdImage $image) { $this->setImageResource($image); imagesavealpha($image, true); } /** * Returns image width. */ public function getWidth(): int { return imagesx($this->image); } /** * Returns image height. */ public function getHeight(): int { return imagesy($this->image); } /** * Sets image resource. */ protected function setImageResource(\GdImage $image): static { $this->image = $image; return $this; } /** * Returns image GD resource. */ public function getImageResource(): \GdImage { return $this->image; } /** * Scales an image. Width and height accept pixels or percent. * @param self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly $mode */ public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static { if ($mode & self::Cover) { return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height); } [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode); if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize $newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource(); imagecopyresampled( $newImage, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->getWidth(), $this->getHeight(), ); $this->image = $newImage; } if ($width < 0 || $height < 0) { imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); } return $this; } /** * Calculates dimensions of resized image. Width and height accept pixels or percent. * @param self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly $mode */ public static function calculateSize( int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $mode = self::OrSmaller, ): array { if ($newWidth === null) { } elseif (self::isPercent($newWidth)) { $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); $percents = true; } else { $newWidth = abs($newWidth); } if ($newHeight === null) { } elseif (self::isPercent($newHeight)) { $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); $mode |= empty($percents) ? 0 : self::Stretch; } else { $newHeight = abs($newHeight); } if ($mode & self::Stretch) { // non-proportional if (!$newWidth || !$newHeight) { throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); } if ($mode & self::ShrinkOnly) { $newWidth = min($srcWidth, $newWidth); $newHeight = min($srcHeight, $newHeight); } } else { // proportional if (!$newWidth && !$newHeight) { throw new Nette\InvalidArgumentException('At least width or height must be specified.'); } $scale = []; if ($newWidth > 0) { // fit width $scale[] = $newWidth / $srcWidth; } if ($newHeight > 0) { // fit height $scale[] = $newHeight / $srcHeight; } if ($mode & self::OrBigger) { $scale = [max($scale)]; } if ($mode & self::ShrinkOnly) { $scale[] = 1; } $scale = min($scale); $newWidth = (int) round($srcWidth * $scale); $newHeight = (int) round($srcHeight * $scale); } return [max($newWidth, 1), max($newHeight, 1)]; } /** * Crops image. Arguments accepts pixels or percent. */ public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static { [$r['x'], $r['y'], $r['width'], $r['height']] = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') { $this->image = imagecrop($this->image, $r); imagesavealpha($this->image, true); } else { $newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource(); imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']); $this->image = $newImage; } return $this; } /** * Calculates dimensions of cutout in image. Arguments accepts pixels or percent. */ public static function calculateCutout( int $srcWidth, int $srcHeight, int|string $left, int|string $top, int|string $newWidth, int|string $newHeight, ): array { if (self::isPercent($newWidth)) { $newWidth = (int) round($srcWidth / 100 * $newWidth); } if (self::isPercent($newHeight)) { $newHeight = (int) round($srcHeight / 100 * $newHeight); } if (self::isPercent($left)) { $left = (int) round(($srcWidth - $newWidth) / 100 * $left); } if (self::isPercent($top)) { $top = (int) round(($srcHeight - $newHeight) / 100 * $top); } if ($left < 0) { $newWidth += $left; $left = 0; } if ($top < 0) { $newHeight += $top; $top = 0; } $newWidth = min($newWidth, $srcWidth - $left); $newHeight = min($newHeight, $srcHeight - $top); return [$left, $top, $newWidth, $newHeight]; } /** * Sharpens image a little bit. */ public function sharpen(): static { imageconvolution($this->image, [ // my magic numbers ;) [-1, -1, -1], [-1, 24, -1], [-1, -1, -1], ], 16, 0); return $this; } /** * Puts another image into this image. Left and top accepts pixels or percent. * @param int $opacity 0..100 */ public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static { $opacity = max(0, min(100, $opacity)); if ($opacity === 0) { return $this; } $width = $image->getWidth(); $height = $image->getHeight(); if (self::isPercent($left)) { $left = (int) round(($this->getWidth() - $width) / 100 * $left); } if (self::isPercent($top)) { $top = (int) round(($this->getHeight() - $height) / 100 * $top); } $output = $input = $image->image; if ($opacity < 100) { $tbl = []; for ($i = 0; $i < 128; $i++) { $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); } $output = imagecreatetruecolor($width, $height); imagealphablending($output, false); if (!$image->isTrueColor()) { $input = $output; imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); } for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $c = \imagecolorat($input, $x, $y); $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); \imagesetpixel($output, $x, $y, $c); } } imagealphablending($output, true); } imagecopy( $this->image, $output, $left, $top, 0, 0, $width, $height, ); return $this; } /** * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). * @throws ImageException */ public function save(string $file, ?int $quality = null, ?int $type = null): void { $type ??= self::extensionToType(pathinfo($file, PATHINFO_EXTENSION)); $this->output($type, $quality, $file); } /** * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). */ public function toString(int $type = self::JPEG, ?int $quality = null): string { return Helpers::capture(function () use ($type, $quality): void { $this->output($type, $quality); }); } /** * Outputs image to string. */ public function __toString(): string { return $this->toString(); } /** * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). * @throws ImageException */ public function send(int $type = self::JPEG, ?int $quality = null): void { header('Content-Type: ' . self::typeToMimeType($type)); $this->output($type, $quality); } /** * Outputs image to browser or file. * @throws ImageException */ private function output(int $type, ?int $quality, ?string $file = null): void { switch ($type) { case self::JPEG: $quality = $quality === null ? 85 : max(0, min(100, $quality)); $success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception break; case self::PNG: $quality = $quality === null ? 9 : max(0, min(9, $quality)); $success = @imagepng($this->image, $file, $quality); // @ is escalated to exception break; case self::GIF: $success = @imagegif($this->image, $file); // @ is escalated to exception break; case self::WEBP: $quality = $quality === null ? 80 : max(0, min(100, $quality)); $success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception break; case self::AVIF: $quality = $quality === null ? 30 : max(0, min(100, $quality)); $success = @imageavif($this->image, $file, $quality); // @ is escalated to exception break; case self::BMP: $success = @imagebmp($this->image, $file); // @ is escalated to exception break; default: throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); } if (!$success) { throw new ImageException(Helpers::getLastError() ?: 'Unknown error'); } } /** * Call to undefined method. * @throws Nette\MemberAccessException */ public function __call(string $name, array $args): mixed { $function = 'image' . $name; if (!function_exists($function)) { ObjectHelpers::strictCall(static::class, $name); } foreach ($args as $key => $value) { if ($value instanceof self) { $args[$key] = $value->getImageResource(); } elseif (is_array($value) && isset($value['red'])) { // rgb $args[$key] = imagecolorallocatealpha( $this->image, $value['red'], $value['green'], $value['blue'], $value['alpha'], ) ?: imagecolorresolvealpha( $this->image, $value['red'], $value['green'], $value['blue'], $value['alpha'], ); } } $res = $function($this->image, ...$args); return $res instanceof \GdImage ? $this->setImageResource($res) : $res; } public function __clone() { ob_start(function () {}); imagepng($this->image, null, 0); $this->setImageResource(imagecreatefromstring(ob_get_clean())); } private static function isPercent(int|string &$num): bool { if (is_string($num) && str_ends_with($num, '%')) { $num = (float) substr($num, 0, -1); return true; } elseif (is_int($num) || $num === (string) (int) $num) { $num = (int) $num; return false; } throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given."); } /** * Prevents serialization. */ public function __sleep(): array { throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); } } PKZO!*!*Utils/Arrays.phpnu[ $array * @param array-key|array-key[] $key * @param ?T $default * @return ?T * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided */ public static function get(array $array, string|int|array $key, mixed $default = null): mixed { foreach (is_array($key) ? $key : [$key] as $k) { if (is_array($array) && array_key_exists($k, $array)) { $array = $array[$k]; } else { if (func_num_args() < 3) { throw new Nette\InvalidArgumentException("Missing item '$k'."); } return $default; } } return $array; } /** * Returns reference to array item. If the index does not exist, new one is created with value null. * @template T * @param array $array * @param array-key|array-key[] $key * @return ?T * @throws Nette\InvalidArgumentException if traversed item is not an array */ public static function &getRef(array &$array, string|int|array $key): mixed { foreach (is_array($key) ? $key : [$key] as $k) { if (is_array($array) || $array === null) { $array = &$array[$k]; } else { throw new Nette\InvalidArgumentException('Traversed item is not an array.'); } } return $array; } /** * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains * the value from the first array in the case of a key collision. * @template T1 * @template T2 * @param array $array1 * @param array $array2 * @return array */ public static function mergeTree(array $array1, array $array2): array { $res = $array1 + $array2; foreach (array_intersect_key($array1, $array2) as $k => $v) { if (is_array($v) && is_array($array2[$k])) { $res[$k] = self::mergeTree($v, $array2[$k]); } } return $res; } /** * Returns zero-indexed position of given array key. Returns null if key is not found. */ public static function getKeyOffset(array $array, string|int $key): ?int { return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true)); } /** * @deprecated use getKeyOffset() */ public static function searchKey(array $array, $key): ?int { return self::getKeyOffset($array, $key); } /** * Tests an array for the presence of value. */ public static function contains(array $array, mixed $value): bool { return in_array($value, $array, true); } /** * Returns the first item from the array or null if array is empty. * @template T * @param array $array * @return ?T */ public static function first(array $array): mixed { return count($array) ? reset($array) : null; } /** * Returns the last item from the array or null if array is empty. * @template T * @param array $array * @return ?T */ public static function last(array $array): mixed { return count($array) ? end($array) : null; } /** * Inserts the contents of the $inserted array into the $array immediately after the $key. * If $key is null (or does not exist), it is inserted at the beginning. */ public static function insertBefore(array &$array, string|int|null $key, array $inserted): void { $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key); $array = array_slice($array, 0, $offset, true) + $inserted + array_slice($array, $offset, count($array), true); } /** * Inserts the contents of the $inserted array into the $array before the $key. * If $key is null (or does not exist), it is inserted at the end. */ public static function insertAfter(array &$array, string|int|null $key, array $inserted): void { if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) { $offset = count($array) - 1; } $array = array_slice($array, 0, $offset + 1, true) + $inserted + array_slice($array, $offset + 1, count($array), true); } /** * Renames key in array. */ public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool { $offset = self::getKeyOffset($array, $oldKey); if ($offset === null) { return false; } $val = &$array[$oldKey]; $keys = array_keys($array); $keys[$offset] = $newKey; $array = array_combine($keys, $array); $array[$newKey] = &$val; return true; } /** * Returns only those array items, which matches a regular expression $pattern. * @param string[] $array * @return string[] */ public static function grep( array $array, #[Language('RegExp')] string $pattern, bool|int $invert = false, ): array { $flags = $invert ? PREG_GREP_INVERT : 0; return Strings::pcre('preg_grep', [$pattern, $array, $flags]); } /** * Transforms multidimensional array to flat array. */ public static function flatten(array $array, bool $preserveKeys = false): array { $res = []; $cb = $preserveKeys ? function ($v, $k) use (&$res): void { $res[$k] = $v; } : function ($v) use (&$res): void { $res[] = $v; }; array_walk_recursive($array, $cb); return $res; } /** * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. */ public static function isList(mixed $value): bool { return is_array($value) && (PHP_VERSION_ID < 80100 ? !$value || array_keys($value) === range(0, count($value) - 1) : array_is_list($value) ); } /** * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. * @param string|string[] $path */ public static function associate(array $array, $path): array|\stdClass { $parts = is_array($path) ? $path : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') { throw new Nette\InvalidArgumentException("Invalid path '$path'."); } $res = $parts[0] === '->' ? new \stdClass : []; foreach ($array as $rowOrig) { $row = (array) $rowOrig; $x = &$res; for ($i = 0; $i < count($parts); $i++) { $part = $parts[$i]; if ($part === '[]') { $x = &$x[]; } elseif ($part === '=') { if (isset($parts[++$i])) { $x = $row[$parts[$i]]; $row = null; } } elseif ($part === '->') { if (isset($parts[++$i])) { if ($x === null) { $x = new \stdClass; } $x = &$x->{$row[$parts[$i]]}; } else { $row = is_object($rowOrig) ? $rowOrig : (object) $row; } } elseif ($part !== '|') { $x = &$x[(string) $row[$part]]; } } if ($x === null) { $x = $row; } } return $res; } /** * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling. */ public static function normalize(array $array, mixed $filling = null): array { $res = []; foreach ($array as $k => $v) { $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; } return $res; } /** * Returns and removes the value of an item from an array. If it does not exist, it throws an exception, * or returns $default, if provided. * @template T * @param array $array * @param ?T $default * @return ?T * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided */ public static function pick(array &$array, string|int $key, mixed $default = null): mixed { if (array_key_exists($key, $array)) { $value = $array[$key]; unset($array[$key]); return $value; } elseif (func_num_args() < 3) { throw new Nette\InvalidArgumentException("Missing item '$key'."); } else { return $default; } } /** * Tests whether at least one element in the array passes the test implemented by the * provided callback with signature `function ($value, $key, array $array): bool`. */ public static function some(iterable $array, callable $callback): bool { foreach ($array as $k => $v) { if ($callback($v, $k, $array)) { return true; } } return false; } /** * Tests whether all elements in the array pass the test implemented by the provided function, * which has the signature `function ($value, $key, array $array): bool`. */ public static function every(iterable $array, callable $callback): bool { foreach ($array as $k => $v) { if (!$callback($v, $k, $array)) { return false; } } return true; } /** * Calls $callback on all elements in the array and returns the array of return values. * The callback has the signature `function ($value, $key, array $array): bool`. */ public static function map(iterable $array, callable $callback): array { $res = []; foreach ($array as $k => $v) { $res[$k] = $callback($v, $k, $array); } return $res; } /** * Invokes all callbacks and returns array of results. * @param callable[] $callbacks */ public static function invoke(iterable $callbacks, ...$args): array { $res = []; foreach ($callbacks as $k => $cb) { $res[$k] = $cb(...$args); } return $res; } /** * Invokes method on every object in an array and returns array of results. * @param object[] $objects */ public static function invokeMethod(iterable $objects, string $method, ...$args): array { $res = []; foreach ($objects as $k => $obj) { $res[$k] = $obj->$method(...$args); } return $res; } /** * Copies the elements of the $array array to the $object object and then returns it. * @template T of object * @param T $object * @return T */ public static function toObject(iterable $array, object $object): object { foreach ($array as $k => $v) { $object->$k = $v; } return $object; } /** * Converts value to array key. */ public static function toKey(mixed $value): int|string { return key([$value => null]); } /** * Returns copy of the $array where every item is converted to string * and prefixed by $prefix and suffixed by $suffix. * @param string[] $array * @return string[] */ public static function wrap(array $array, string $prefix = '', string $suffix = ''): array { $res = []; foreach ($array as $k => $v) { $res[$k] = $prefix . $v . $suffix; } return $res; } } PKZ8Utils/Type.phpnu[ */ private array $types; private bool $simple; private string $kind; // | & /** * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name. * If the subject has no type, it returns null. */ public static function fromReflection( \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, ): ?self { $type = $reflection instanceof \ReflectionFunctionAbstract ? $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $reflection instanceof \ReflectionMethod ? $reflection->getTentativeReturnType() : null) : $reflection->getType(); return $type ? self::fromReflectionType($type, $reflection, true) : null; } private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string { if ($type instanceof \ReflectionNamedType) { $name = self::resolve($type->getName(), $of); return $asObject ? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name]) : $name; } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { return new self( array_map(fn($t) => self::fromReflectionType($t, $of, false), $type->getTypes()), $type instanceof \ReflectionUnionType ? '|' : '&', ); } else { throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of)); } } /** * Creates the Type object according to the text notation. */ public static function fromString(string $type): self { if (!Validators::isTypeDeclaration($type)) { throw new Nette\InvalidArgumentException("Invalid type '$type'."); } if ($type[0] === '?') { return new self([substr($type, 1), 'null']); } $unions = []; foreach (explode('|', $type) as $part) { $part = explode('&', trim($part, '()')); $unions[] = count($part) === 1 ? $part[0] : new self($part, '&'); } return count($unions) === 1 && $unions[0] instanceof self ? $unions[0] : new self($unions); } /** * Resolves 'self', 'static' and 'parent' to the actual class name. */ public static function resolve( string $type, \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of, ): string { $lower = strtolower($type); if ($of instanceof \ReflectionFunction) { return $type; } elseif ($lower === 'self' || $lower === 'static') { return $of->getDeclaringClass()->name; } elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) { return $of->getDeclaringClass()->getParentClass()->name; } else { return $type; } } private function __construct(array $types, string $kind = '|') { $o = array_search('null', $types, true); if ($o !== false) { // null as last array_splice($types, $o, 1); $types[] = 'null'; } $this->types = $types; $this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null'; $this->kind = count($types) > 1 ? $kind : ''; } public function __toString(): string { $multi = count($this->types) > 1; if ($this->simple) { return ($multi ? '?' : '') . $this->types[0]; } $res = []; foreach ($this->types as $type) { $res[] = $type instanceof self && $multi ? "($type)" : $type; } return implode($this->kind, $res); } /** * Returns the array of subtypes that make up the compound type as strings. * @return array */ public function getNames(): array { return array_map(fn($t) => $t instanceof self ? $t->getNames() : $t, $this->types); } /** * Returns the array of subtypes that make up the compound type as Type objects: * @return self[] */ public function getTypes(): array { return array_map(fn($t) => $t instanceof self ? $t : new self([$t]), $this->types); } /** * Returns the type name for simple types, otherwise null. */ public function getSingleName(): ?string { return $this->simple ? $this->types[0] : null; } /** * Returns true whether it is a union type. */ public function isUnion(): bool { return $this->kind === '|'; } /** * Returns true whether it is an intersection type. */ public function isIntersection(): bool { return $this->kind === '&'; } /** * Returns true whether it is a simple type. Single nullable types are also considered to be simple types. */ public function isSimple(): bool { return $this->simple; } /** @deprecated use isSimple() */ public function isSingle(): bool { return $this->simple; } /** * Returns true whether the type is both a simple and a PHP built-in type. */ public function isBuiltin(): bool { return $this->simple && Validators::isBuiltinType($this->types[0]); } /** * Returns true whether the type is both a simple and a class name. */ public function isClass(): bool { return $this->simple && !Validators::isBuiltinType($this->types[0]); } /** * Determines if type is special class name self/parent/static. */ public function isClassKeyword(): bool { return $this->simple && Validators::isClassKeyword($this->types[0]); } /** * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter. */ public function allows(string $subtype): bool { if ($this->types === ['mixed']) { return true; } $subtype = self::fromString($subtype); return $subtype->isUnion() ? Arrays::every($subtype->types, fn($t) => $this->allows2($t instanceof self ? $t->types : [$t])) : $this->allows2($subtype->types); } private function allows2(array $subtypes): bool { return $this->isUnion() ? Arrays::some($this->types, fn($t) => $this->allows3($t instanceof self ? $t->types : [$t], $subtypes)) : $this->allows3($this->types, $subtypes); } private function allows3(array $types, array $subtypes): bool { return Arrays::every( $types, fn($type) => Arrays::some( $subtypes, fn($subtype) => Validators::isBuiltinType($type) ? strcasecmp($type, $subtype) === 0 : is_a($subtype, $type, true) ) ); } } PKZsvW(W(Utils/Validators.phpnu[ 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, 'never' => 1, 'true' => 1, ]; /** @var array */ protected static $validators = [ // PHP types 'array' => 'is_array', 'bool' => 'is_bool', 'boolean' => 'is_bool', 'float' => 'is_float', 'int' => 'is_int', 'integer' => 'is_int', 'null' => 'is_null', 'object' => 'is_object', 'resource' => 'is_resource', 'scalar' => 'is_scalar', 'string' => 'is_string', // pseudo-types 'callable' => [self::class, 'isCallable'], 'iterable' => 'is_iterable', 'list' => [Arrays::class, 'isList'], 'mixed' => [self::class, 'isMixed'], 'none' => [self::class, 'isNone'], 'number' => [self::class, 'isNumber'], 'numeric' => [self::class, 'isNumeric'], 'numericint' => [self::class, 'isNumericInt'], // string patterns 'alnum' => 'ctype_alnum', 'alpha' => 'ctype_alpha', 'digit' => 'ctype_digit', 'lower' => 'ctype_lower', 'pattern' => null, 'space' => 'ctype_space', 'unicode' => [self::class, 'isUnicode'], 'upper' => 'ctype_upper', 'xdigit' => 'ctype_xdigit', // syntax validation 'email' => [self::class, 'isEmail'], 'identifier' => [self::class, 'isPhpIdentifier'], 'uri' => [self::class, 'isUri'], 'url' => [self::class, 'isUrl'], // environment validation 'class' => 'class_exists', 'interface' => 'interface_exists', 'directory' => 'is_dir', 'file' => 'is_file', 'type' => [self::class, 'isType'], ]; /** @var array */ protected static $counters = [ 'string' => 'strlen', 'unicode' => [Strings::class, 'length'], 'array' => 'count', 'list' => 'count', 'alnum' => 'strlen', 'alpha' => 'strlen', 'digit' => 'strlen', 'lower' => 'strlen', 'space' => 'strlen', 'upper' => 'strlen', 'xdigit' => 'strlen', ]; /** * Verifies that the value is of expected types separated by pipe. * @throws AssertionException */ public static function assert(mixed $value, string $expected, string $label = 'variable'): void { if (!static::is($value, $expected)) { $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null']; $type = $translate[gettype($value)] ?? gettype($value); if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { $type .= ' ' . var_export($value, true); } elseif (is_object($value)) { $type .= ' ' . $value::class; } throw new AssertionException("The $label expects to be $expected, $type given."); } } /** * Verifies that element $key in array is of expected types separated by pipe. * @param mixed[] $array * @throws AssertionException */ public static function assertField( array $array, $key, ?string $expected = null, string $label = "item '%' in array", ): void { if (!array_key_exists($key, $array)) { throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.'); } elseif ($expected) { static::assert($array[$key], $expected, str_replace('%', $key, $label)); } } /** * Verifies that the value is of expected types separated by pipe. */ public static function is(mixed $value, string $expected): bool { foreach (explode('|', $expected) as $item) { if (str_ends_with($item, '[]')) { if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { return true; } continue; } elseif (str_starts_with($item, '?')) { $item = substr($item, 1); if ($value === null) { return true; } } [$type] = $item = explode(':', $item, 2); if (isset(static::$validators[$type])) { try { if (!static::$validators[$type]($value)) { continue; } } catch (\TypeError $e) { continue; } } elseif ($type === 'pattern') { if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { return true; } continue; } elseif (!$value instanceof $type) { continue; } if (isset($item[1])) { $length = $value; if (isset(static::$counters[$type])) { $length = static::$counters[$type]($value); } $range = explode('..', $item[1]); if (!isset($range[1])) { $range[1] = $range[0]; } if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) { continue; } } return true; } return false; } /** * Finds whether all values are of expected types separated by pipe. * @param mixed[] $values */ public static function everyIs(iterable $values, string $expected): bool { foreach ($values as $value) { if (!static::is($value, $expected)) { return false; } } return true; } /** * Checks if the value is an integer or a float. */ public static function isNumber(mixed $value): bool { return is_int($value) || is_float($value); } /** * Checks if the value is an integer or a integer written in a string. */ public static function isNumericInt(mixed $value): bool { return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); } /** * Checks if the value is a number or a number written in a string. */ public static function isNumeric(mixed $value): bool { return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value)); } /** * Checks if the value is a syntactically correct callback. */ public static function isCallable(mixed $value): bool { return $value && is_callable($value, true); } /** * Checks if the value is a valid UTF-8 string. */ public static function isUnicode(mixed $value): bool { return is_string($value) && preg_match('##u', $value); } /** * Checks if the value is 0, '', false or null. */ public static function isNone(mixed $value): bool { return $value == null; // intentionally == } /** @internal */ public static function isMixed(): bool { return true; } /** * Checks if a variable is a zero-based integer indexed array. * @deprecated use Nette\Utils\Arrays::isList */ public static function isList(mixed $value): bool { return Arrays::isList($value); } /** * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null). * Numbers, strings and DateTime objects can be compared. */ public static function isInRange(mixed $value, array $range): bool { if ($value === null || !(isset($range[0]) || isset($range[1]))) { return false; } $limit = $range[0] ?? $range[1]; if (is_string($limit)) { $value = (string) $value; } elseif ($limit instanceof \DateTimeInterface) { if (!$value instanceof \DateTimeInterface) { return false; } } elseif (is_numeric($value)) { $value *= 1; } else { return false; } return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1])); } /** * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. */ public static function isEmail(string $value): bool { $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part $alpha = "a-z\x80-\xFF"; // superset of IDN return (bool) preg_match(<< \\? (? [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) | (? (?&type) (& (?&type))+ ) | (? (?&type) | \( (?&intersection) \) ) (\| (?&upart))+ )$~xAD XX, $type); } } PKZdUtils/exceptions.phpnu[ $array */ public static function from(array $array, bool $recursive = true): static { $obj = new static; foreach ($array as $key => $value) { $obj->$key = $recursive && is_array($value) ? static::from($value, true) : $value; } return $obj; } /** * Returns an iterator over all items. * @return \Iterator */ public function &getIterator(): \Iterator { foreach ((array) $this as $key => $foo) { yield $key => $this->$key; } } /** * Returns items count. */ public function count(): int { return count((array) $this); } /** * Replaces or appends a item. * @param string|int $key * @param T $value */ public function offsetSet($key, $value): void { if (!is_scalar($key)) { // prevents null throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key))); } $this->$key = $value; } /** * Returns a item. * @param string|int $key * @return T */ #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->$key; } /** * Determines whether a item exists. * @param string|int $key */ public function offsetExists($key): bool { return isset($this->$key); } /** * Removes the element from this list. * @param string|int $key */ public function offsetUnset($key): void { unset($this->$key); } } PKZ Utils/DateTime.phpnu[format('Y-m-d H:i:s.u'), $time->getTimezone()); } elseif (is_numeric($time)) { if ($time <= self::YEAR) { $time += time(); } return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get())); } else { // textual or null return new static((string) $time); } } /** * Creates DateTime object. * @throws Nette\InvalidArgumentException if the date and time are not valid. */ public static function fromParts( int $year, int $month, int $day, int $hour = 0, int $minute = 0, float $second = 0.0, ): static { $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); if ( !checkdate($month, $day, $year) || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second >= 60 ) { throw new Nette\InvalidArgumentException("Invalid date '$s'"); } return new static($s); } /** * Returns new DateTime object formatted according to the specified format. */ public static function createFromFormat( string $format, string $time, string|\DateTimeZone|null $timezone = null, ): static|false { if ($timezone === null) { $timezone = new \DateTimeZone(date_default_timezone_get()); } elseif (is_string($timezone)) { $timezone = new \DateTimeZone($timezone); } $date = parent::createFromFormat($format, $time, $timezone); return $date ? static::from($date) : false; } /** * Returns JSON representation in ISO 8601 (used by JavaScript). */ public function jsonSerialize(): string { return $this->format('c'); } /** * Returns the date and time in the format 'Y-m-d H:i:s'. */ public function __toString(): string { return $this->format('Y-m-d H:i:s'); } /** * Creates a copy with a modified time. */ public function modifyClone(string $modify = ''): static { $dolly = clone $this; return $modify ? $dolly->modify($modify) : $dolly; } } PKZ9 Utils/Helpers.phpnu[ $max) { throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max)."); } return min(max($value, $min), $max); } /** * Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding). * @param string[] $possibilities */ public static function getSuggestion(array $possibilities, string $value): ?string { $best = null; $min = (strlen($value) / 4 + 1) * 10 + .1; foreach (array_unique($possibilities) as $item) { if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) { $min = $len; $best = $item; } } return $best; } /** * Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <> */ public static function compare(mixed $left, string $operator, mixed $right): bool { return match ($operator) { '>' => $left > $right, '>=' => $left >= $right, '<' => $left < $right, '<=' => $left <= $right, '=', '==' => $left == $right, '===' => $left === $right, '!=', '<>' => $left != $right, '!==' => $left !== $right, default => throw new Nette\InvalidArgumentException("Unknown operator '$operator'"), }; } } PKZsBLLUtils/Html.phpnu[ element's attributes */ public $attrs = []; /** void elements */ public static $emptyElements = [ 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, ]; /** @var array nodes */ protected $children = []; /** element's name */ private string $name = ''; private bool $isEmpty = false; /** * Constructs new HTML element. * @param array|string $attrs element's attributes or plain text content */ public static function el(?string $name = null, array|string|null $attrs = null): static { $el = new static; $parts = explode(' ', (string) $name, 2); $el->setName($parts[0]); if (is_array($attrs)) { $el->attrs = $attrs; } elseif ($attrs !== null) { $el->setText($attrs); } if (isset($parts[1])) { foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) { $el->attrs[$m[1]] = $m[3] ?? true; } } return $el; } /** * Returns an object representing HTML text. */ public static function fromHtml(string $html): static { return (new static)->setHtml($html); } /** * Returns an object representing plain text. */ public static function fromText(string $text): static { return (new static)->setText($text); } /** * Converts to HTML. */ final public function toHtml(): string { return $this->render(); } /** * Converts to plain text. */ final public function toText(): string { return $this->getText(); } /** * Converts given HTML code to plain text. */ public static function htmlToText(string $html): string { return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8'); } /** * Changes element's name. */ final public function setName(string $name, ?bool $isEmpty = null): static { $this->name = $name; $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); return $this; } /** * Returns element's name. */ final public function getName(): string { return $this->name; } /** * Is element empty? */ final public function isEmpty(): bool { return $this->isEmpty; } /** * Sets multiple attributes. */ public function addAttributes(array $attrs): static { $this->attrs = array_merge($this->attrs, $attrs); return $this; } /** * Appends value to element's attribute. */ public function appendAttribute(string $name, mixed $value, mixed $option = true): static { if (is_array($value)) { $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; $this->attrs[$name] = $value + $prev; } elseif ((string) $value === '') { $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array $this->attrs[$name][$value] = $option; } else { $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option]; } return $this; } /** * Sets element's attribute. */ public function setAttribute(string $name, mixed $value): static { $this->attrs[$name] = $value; return $this; } /** * Returns element's attribute. */ public function getAttribute(string $name): mixed { return $this->attrs[$name] ?? null; } /** * Unsets element's attribute. */ public function removeAttribute(string $name): static { unset($this->attrs[$name]); return $this; } /** * Unsets element's attributes. */ public function removeAttributes(array $attributes): static { foreach ($attributes as $name) { unset($this->attrs[$name]); } return $this; } /** * Overloaded setter for element's attribute. */ final public function __set(string $name, mixed $value): void { $this->attrs[$name] = $value; } /** * Overloaded getter for element's attribute. */ final public function &__get(string $name): mixed { return $this->attrs[$name]; } /** * Overloaded tester for element's attribute. */ final public function __isset(string $name): bool { return isset($this->attrs[$name]); } /** * Overloaded unsetter for element's attribute. */ final public function __unset(string $name): void { unset($this->attrs[$name]); } /** * Overloaded setter for element's attribute. */ final public function __call(string $m, array $args): mixed { $p = substr($m, 0, 3); if ($p === 'get' || $p === 'set' || $p === 'add') { $m = substr($m, 3); $m[0] = $m[0] | "\x20"; if ($p === 'get') { return $this->attrs[$m] ?? null; } elseif ($p === 'add') { $args[] = true; } } if (count($args) === 0) { // invalid } elseif (count($args) === 1) { // set $this->attrs[$m] = $args[0]; } else { // add $this->appendAttribute($m, $args[0], $args[1]); } return $this; } /** * Special setter for element's attribute. */ final public function href(string $path, array $query = []): static { if ($query) { $query = http_build_query($query, '', '&'); if ($query !== '') { $path .= '?' . $query; } } $this->attrs['href'] = $path; return $this; } /** * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. */ public function data(string $name, mixed $value = null): static { if (func_num_args() === 1) { $this->attrs['data'] = $name; } else { $this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value; } return $this; } /** * Sets element's HTML content. */ final public function setHtml(mixed $html): static { $this->children = [(string) $html]; return $this; } /** * Returns element's HTML content. */ final public function getHtml(): string { return implode('', $this->children); } /** * Sets element's textual content. */ final public function setText(mixed $text): static { if (!$text instanceof HtmlStringable) { $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); } $this->children = [(string) $text]; return $this; } /** * Returns element's textual content. */ final public function getText(): string { return self::htmlToText($this->getHtml()); } /** * Adds new element's child. */ final public function addHtml(mixed $child): static { return $this->insert(null, $child); } /** * Appends plain-text string to element content. */ public function addText(mixed $text): static { if (!$text instanceof HtmlStringable) { $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); } return $this->insert(null, $text); } /** * Creates and adds a new Html child. */ final public function create(string $name, array|string|null $attrs = null): static { $this->insert(null, $child = static::el($name, $attrs)); return $child; } /** * Inserts child node. */ public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static { $child = $child instanceof self ? $child : (string) $child; if ($index === null) { // append $this->children[] = $child; } else { // insert or replace array_splice($this->children, $index, $replace ? 1 : 0, [$child]); } return $this; } /** * Inserts (replaces) child node (\ArrayAccess implementation). * @param int|null $index position or null for appending * @param Html|string $child Html node or raw HTML string */ final public function offsetSet($index, $child): void { $this->insert($index, $child, true); } /** * Returns child node (\ArrayAccess implementation). * @param int $index */ final public function offsetGet($index): HtmlStringable|string { return $this->children[$index]; } /** * Exists child node? (\ArrayAccess implementation). * @param int $index */ final public function offsetExists($index): bool { return isset($this->children[$index]); } /** * Removes child node (\ArrayAccess implementation). * @param int $index */ public function offsetUnset($index): void { if (isset($this->children[$index])) { array_splice($this->children, $index, 1); } } /** * Returns children count. */ final public function count(): int { return count($this->children); } /** * Removes all children. */ public function removeChildren(): void { $this->children = []; } /** * Iterates over elements. * @return \ArrayIterator */ final public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->children); } /** * Returns all children. */ final public function getChildren(): array { return $this->children; } /** * Renders element's start tag, content and end tag. */ final public function render(?int $indent = null): string { $s = $this->startTag(); if (!$this->isEmpty) { // add content if ($indent !== null) { $indent++; } foreach ($this->children as $child) { if ($child instanceof self) { $s .= $child->render($indent); } else { $s .= $child; } } // add end tag $s .= $this->endTag(); } if ($indent !== null) { return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2)); } return $s; } final public function __toString(): string { return $this->render(); } /** * Returns element's start tag. */ final public function startTag(): string { return $this->name ? '<' . $this->name . $this->attributes() . '>' : ''; } /** * Returns element's end tag. */ final public function endTag(): string { return $this->name && !$this->isEmpty ? 'name . '>' : ''; } /** * Returns element's attributes. * @internal */ final public function attributes(): string { if (!is_array($this->attrs)) { return ''; } $s = ''; $attrs = $this->attrs; foreach ($attrs as $key => $value) { if ($value === null || $value === false) { continue; } elseif ($value === true) { $s .= ' ' . $key; continue; } elseif (is_array($value)) { if (strncmp($key, 'data-', 5) === 0) { $value = Json::encode($value); } else { $tmp = null; foreach ($value as $k => $v) { if ($v != null) { // intentionally ==, skip nulls & empty string // composite 'style' vs. 'others' $tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v); } } if ($tmp === null) { continue; } $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp); } } elseif (is_float($value)) { $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); } else { $value = (string) $value; } $q = str_contains($value, '"') ? "'" : '"'; $s .= ' ' . $key . '=' . $q . str_replace( ['&', $q, '<'], ['&', $q === '"' ? '"' : ''', '<'], $value, ) . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '') . $q; } $s = str_replace('@', '@', $s); return $s; } /** * Clones all children too. */ public function __clone() { foreach ($this->children as $key => $value) { if (is_object($value)) { $this->children[$key] = clone $value; } } } } PKZzd!d!Utils/Reflection.phpnu[isDefaultValueConstant()) { $const = $orig = $param->getDefaultValueConstantName(); $pair = explode('::', $const); if (isset($pair[1])) { $pair[0] = Type::resolve($pair[0], $param); try { $rcc = new \ReflectionClassConstant($pair[0], $pair[1]); } catch (\ReflectionException $e) { $name = self::toString($param); throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e); } return $rcc->getValue(); } elseif (!defined($const)) { $const = substr((string) strrchr($const, '\\'), 1); if (!defined($const)) { $name = self::toString($param); throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name."); } } return constant($const); } return $param->getDefaultValue(); } /** * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait. */ public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass { foreach ($prop->getDeclaringClass()->getTraits() as $trait) { if ($trait->hasProperty($prop->name) // doc-comment guessing as workaround for insufficient PHP reflection && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment() ) { return self::getPropertyDeclaringClass($trait->getProperty($prop->name)); } } return $prop->getDeclaringClass(); } /** * Returns a reflection of a method that contains a declaration of $method. * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name. */ public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod { // file & line guessing as workaround for insufficient PHP reflection $decl = $method->getDeclaringClass(); if ($decl->getFileName() === $method->getFileName() && $decl->getStartLine() <= $method->getStartLine() && $decl->getEndLine() >= $method->getEndLine() ) { return $method; } $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()]; if (($alias = $decl->getTraitAliases()[$method->name] ?? null) && ($m = new \ReflectionMethod($alias)) && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] ) { return self::getMethodDeclaringMethod($m); } foreach ($decl->getTraits() as $trait) { if ($trait->hasMethod($method->name) && ($m = $trait->getMethod($method->name)) && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] ) { return self::getMethodDeclaringMethod($m); } } return $method; } /** * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache. */ public static function areCommentsAvailable(): bool { static $res; return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment(); } public static function toString(\Reflector $ref): string { if ($ref instanceof \ReflectionClass) { return $ref->name; } elseif ($ref instanceof \ReflectionMethod) { return $ref->getDeclaringClass()->name . '::' . $ref->name . '()'; } elseif ($ref instanceof \ReflectionFunction) { return $ref->name . '()'; } elseif ($ref instanceof \ReflectionProperty) { return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name; } elseif ($ref instanceof \ReflectionParameter) { return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()); } else { throw new Nette\InvalidArgumentException; } } /** * Expands the name of the class to full name in the given context of given class. * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context. * @throws Nette\InvalidArgumentException */ public static function expandClassName(string $name, \ReflectionClass $context): string { $lower = strtolower($name); if (empty($name)) { throw new Nette\InvalidArgumentException('Class name must not be empty.'); } elseif (Validators::isBuiltinType($lower)) { return $lower; } elseif ($lower === 'self' || $lower === 'static') { return $context->name; } elseif ($lower === 'parent') { return $context->getParentClass() ? $context->getParentClass()->name : 'parent'; } elseif ($name[0] === '\\') { // fully qualified name return ltrim($name, '\\'); } $uses = self::getUseStatements($context); $parts = explode('\\', $name, 2); if (isset($uses[$parts[0]])) { $parts[0] = $uses[$parts[0]]; return implode('\\', $parts); } elseif ($context->inNamespace()) { return $context->getNamespaceName() . '\\' . $name; } else { return $name; } } /** @return array of [alias => class] */ public static function getUseStatements(\ReflectionClass $class): array { if ($class->isAnonymous()) { throw new Nette\NotImplementedException('Anonymous classes are not supported.'); } static $cache = []; if (!isset($cache[$name = $class->name])) { if ($class->isInternal()) { $cache[$name] = []; } else { $code = file_get_contents($class->getFileName()); $cache = self::parseUseStatements($code, $name) + $cache; } } return $cache[$name]; } /** * Parses PHP code to [class => [alias => class, ...]] */ private static function parseUseStatements(string $code, ?string $forClass = null): array { try { $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); } catch (\ParseError $e) { trigger_error($e->getMessage(), E_USER_NOTICE); $tokens = []; } $namespace = $class = $classLevel = $level = null; $res = $uses = []; $nameTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; while ($token = current($tokens)) { next($tokens); switch ($token->id) { case T_NAMESPACE: $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\'); $uses = []; break; case T_CLASS: case T_INTERFACE: case T_TRAIT: case PHP_VERSION_ID < 80100 ? T_CLASS : T_ENUM: if ($name = self::fetch($tokens, T_STRING)) { $class = $namespace . $name; $classLevel = $level + 1; $res[$class] = $uses; if ($class === $forClass) { return $res; } } break; case T_USE: while (!$class && ($name = self::fetch($tokens, $nameTokens))) { $name = ltrim($name, '\\'); if (self::fetch($tokens, '{')) { while ($suffix = self::fetch($tokens, $nameTokens)) { if (self::fetch($tokens, T_AS)) { $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; } else { $tmp = explode('\\', $suffix); $uses[end($tmp)] = $name . $suffix; } if (!self::fetch($tokens, ',')) { break; } } } elseif (self::fetch($tokens, T_AS)) { $uses[self::fetch($tokens, T_STRING)] = $name; } else { $tmp = explode('\\', $name); $uses[end($tmp)] = $name; } if (!self::fetch($tokens, ',')) { break; } } break; case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: case ord('{'): $level++; break; case ord('}'): if ($level === $classLevel) { $class = $classLevel = null; } $level--; } } return $res; } private static function fetch(array &$tokens, string|int|array $take): ?string { $res = null; while ($token = current($tokens)) { if ($token->is($take)) { $res .= $token->text; } elseif (!$token->is([T_DOC_COMMENT, T_WHITESPACE, T_COMMENT])) { break; } next($tokens); } return $res; } } PKZ4+Utils/Json.phpnu[page = $page; return $this; } /** * Returns current page number. */ public function getPage(): int { return $this->base + $this->getPageIndex(); } /** * Returns first page number. */ public function getFirstPage(): int { return $this->base; } /** * Returns last page number. */ public function getLastPage(): ?int { return $this->itemCount === null ? null : $this->base + max(0, $this->getPageCount() - 1); } /** * Returns the sequence number of the first element on the page */ public function getFirstItemOnPage(): int { return $this->itemCount !== 0 ? $this->offset + 1 : 0; } /** * Returns the sequence number of the last element on the page */ public function getLastItemOnPage(): int { return $this->offset + $this->length; } /** * Sets first page (base) number. */ public function setBase(int $base): static { $this->base = $base; return $this; } /** * Returns first page (base) number. */ public function getBase(): int { return $this->base; } /** * Returns zero-based page number. */ protected function getPageIndex(): int { $index = max(0, $this->page - $this->base); return $this->itemCount === null ? $index : min($index, max(0, $this->getPageCount() - 1)); } /** * Is the current page the first one? */ public function isFirst(): bool { return $this->getPageIndex() === 0; } /** * Is the current page the last one? */ public function isLast(): bool { return $this->itemCount === null ? false : $this->getPageIndex() >= $this->getPageCount() - 1; } /** * Returns the total number of pages. */ public function getPageCount(): ?int { return $this->itemCount === null ? null : (int) ceil($this->itemCount / $this->itemsPerPage); } /** * Sets the number of items to display on a single page. */ public function setItemsPerPage(int $itemsPerPage): static { $this->itemsPerPage = max(1, $itemsPerPage); return $this; } /** * Returns the number of items to display on a single page. */ public function getItemsPerPage(): int { return $this->itemsPerPage; } /** * Sets the total number of items. */ public function setItemCount(?int $itemCount = null): static { $this->itemCount = $itemCount === null ? null : max(0, $itemCount); return $this; } /** * Returns the total number of items. */ public function getItemCount(): ?int { return $this->itemCount; } /** * Returns the absolute index of the first item on current page. */ public function getOffset(): int { return $this->getPageIndex() * $this->itemsPerPage; } /** * Returns the absolute index of the first item on current page in countdown paging. */ public function getCountdownOffset(): ?int { return $this->itemCount === null ? null : max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage); } /** * Returns the number of items on current page. */ public function getLength(): int { return $this->itemCount === null ? $this->itemsPerPage : min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage); } } PKZf-A3A3Utils/Finder.phpnu[size('> 10kB') * ->from('.') * ->exclude('temp'); * * @implements \IteratorAggregate */ class Finder implements \IteratorAggregate { use Nette\SmartObject; /** @var array */ private array $find = []; /** @var string[] */ private array $in = []; /** @var \Closure[] */ private array $filters = []; /** @var \Closure[] */ private array $descentFilters = []; /** @var array */ private array $appends = []; private bool $childFirst = false; /** @var ?callable */ private $sort; private int $maxDepth = -1; private bool $ignoreUnreadableDirs = true; /** * Begins search for files and directories matching mask. */ public static function find(string|array $masks): static { $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic return (new static)->addMask($masks, 'dir')->addMask($masks, 'file'); } /** * Begins search for files matching mask. */ public static function findFiles(string|array $masks): static { $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic return (new static)->addMask($masks, 'file'); } /** * Begins search for directories matching mask. */ public static function findDirectories(string|array $masks): static { $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic return (new static)->addMask($masks, 'dir'); } /** * Finds files matching the specified masks. */ public function files(string|array $masks): static { return $this->addMask((array) $masks, 'file'); } /** * Finds directories matching the specified masks. */ public function directories(string|array $masks): static { return $this->addMask((array) $masks, 'dir'); } private function addMask(array $masks, string $mode): static { foreach ($masks as $mask) { $mask = FileSystem::unixSlashes($mask); if ($mode === 'dir') { $mask = rtrim($mask, '/'); } if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) { throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); } if (str_starts_with($mask, '**/')) { $mask = substr($mask, 3); } $this->find[] = [$mask, $mode]; } return $this; } /** * Searches in the given directories. Wildcards are allowed. */ public function in(string|array $paths): static { $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic $this->addLocation($paths, ''); return $this; } /** * Searches recursively from the given directories. Wildcards are allowed. */ public function from(string|array $paths): static { $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic $this->addLocation($paths, '/**'); return $this; } private function addLocation(array $paths, string $ext): void { foreach ($paths as $path) { if ($path === '') { throw new Nette\InvalidArgumentException("Invalid directory '$path'"); } $path = rtrim(FileSystem::unixSlashes($path), '/'); $this->in[] = $path . $ext; } } /** * Lists directory's contents before the directory itself. By default, this is disabled. */ public function childFirst(bool $state = true): static { $this->childFirst = $state; return $this; } /** * Ignores unreadable directories. By default, this is enabled. */ public function ignoreUnreadableDirs(bool $state = true): static { $this->ignoreUnreadableDirs = $state; return $this; } /** * Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory. * @param callable(FileInfo, FileInfo): int $callback */ public function sortBy(callable $callback): static { $this->sort = $callback; return $this; } /** * Sorts files in each directory naturally by name. */ public function sortByName(): static { $this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename()); return $this; } /** * Adds the specified paths or appends a new finder that returns. */ public function append(string|array|null $paths = null): static { if ($paths === null) { return $this->appends[] = new static; } $this->appends = array_merge($this->appends, (array) $paths); return $this; } /********************* filtering ****************d*g**/ /** * Skips entries that matches the given masks relative to the ones defined with the in() or from() methods. */ public function exclude(string|array $masks): static { $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic foreach ($masks as $mask) { $mask = FileSystem::unixSlashes($mask); if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) { throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); } $end = $m[3]; $re = $this->buildPattern($m[2]); $filter = fn(FileInfo $file): bool => ($end && !$file->isDir()) || !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname())); $this->descentFilter($filter); if ($end !== '/*') { $this->filter($filter); } } return $this; } /** * Yields only entries which satisfy the given filter. * @param callable(FileInfo): bool $callback */ public function filter(callable $callback): static { $this->filters[] = \Closure::fromCallable($callback); return $this; } /** * It descends only to directories that match the specified filter. * @param callable(FileInfo): bool $callback */ public function descentFilter(callable $callback): static { $this->descentFilters[] = \Closure::fromCallable($callback); return $this; } /** * Sets the maximum depth of entries. */ public function limitDepth(?int $depth): static { $this->maxDepth = $depth ?? -1; return $this; } /** * Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB */ public function size(string $operator, ?int $size = null): static { if (func_num_args() === 1) { // in $operator is predicate if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) { throw new Nette\InvalidArgumentException('Invalid size predicate format.'); } [, $operator, $size, $unit] = $matches; $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9]; $size *= $units[strtolower($unit)]; $operator = $operator ?: '='; } return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size)); } /** * Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23 */ public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static { if (func_num_args() === 1) { // in $operator is predicate if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) { throw new Nette\InvalidArgumentException('Invalid date predicate format.'); } [, $operator, $date] = $matches; $operator = $operator ?: '='; } $date = DateTime::from($date)->format('U'); return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date)); } /********************* iterator generator ****************d*g**/ /** * Returns an array with all found files and directories. */ public function collect(): array { return iterator_to_array($this->getIterator()); } /** @return \Generator */ public function getIterator(): \Generator { $plan = $this->buildPlan(); foreach ($plan as $dir => $searches) { yield from $this->traverseDir($dir, $searches); } foreach ($this->appends as $item) { if ($item instanceof self) { yield from $item->getIterator(); } else { $item = FileSystem::platformSlashes($item); yield $item => new FileInfo($item); } } } /** * @param array<\stdClass{pattern: string, mode: string, recursive: bool}> $searches * @param string[] $subdirs * @return \Generator */ private function traverseDir(string $dir, array $searches, array $subdirs = []): \Generator { if ($this->maxDepth >= 0 && count($subdirs) > $this->maxDepth) { return; } elseif (!is_dir($dir)) { throw new Nette\InvalidStateException("Directory '$dir' not found."); } try { $pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS); } catch (\UnexpectedValueException $e) { if ($this->ignoreUnreadableDirs) { return; } else { throw new Nette\InvalidStateException($e->getMessage()); } } $files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir)); if ($this->sort) { $files = iterator_to_array($files); usort($files, $this->sort); } foreach ($files as $file) { $pathName = $file->getPathname(); $cache = $subSearch = []; if ($file->isDir()) { foreach ($searches as $search) { if ($search->recursive && $this->proveFilters($this->descentFilters, $file, $cache)) { $subSearch[] = $search; } } } if ($this->childFirst && $subSearch) { yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); } $relativePathname = FileSystem::unixSlashes($file->getRelativePathname()); foreach ($searches as $search) { if ( $file->getType() === $search->mode && preg_match($search->pattern, $relativePathname) && $this->proveFilters($this->filters, $file, $cache) ) { yield $pathName => $file; break; } } if (!$this->childFirst && $subSearch) { yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); } } } private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator { foreach ($pathNames as $pathName) { if (!$absolute) { $pathName = preg_replace('~\.?/~A', '', $pathName); } $pathName = FileSystem::platformSlashes($pathName); yield new FileInfo($pathName, $relativePath); } } private function proveFilters(array $filters, FileInfo $file, array &$cache): bool { foreach ($filters as $filter) { $res = &$cache[spl_object_id($filter)]; $res ??= $filter($file); if (!$res) { return false; } } return true; } /** @return array> */ private function buildPlan(): array { $plan = $dirCache = []; foreach ($this->find as [$mask, $mode]) { $splits = []; if (FileSystem::isAbsolute($mask)) { if ($this->in) { throw new Nette\InvalidStateException("You cannot combine the absolute path in the mask '$mask' and the directory to search '{$this->in[0]}'."); } $splits[] = self::splitRecursivePart($mask); } else { foreach ($this->in ?: ['.'] as $in) { $in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob() $splits[] = self::splitRecursivePart($in . '/' . $mask); } } foreach ($splits as [$base, $rest, $recursive]) { $base = $base === '' ? '.' : $base; $dirs = $dirCache[$base] ??= strpbrk($base, '*?[') ? glob($base, GLOB_NOSORT | GLOB_ONLYDIR | GLOB_NOESCAPE) : [strtr($base, ['[[]' => '[', '[]]' => ']'])]; // unescape [ and ] $search = (object) ['pattern' => $this->buildPattern($rest), 'mode' => $mode, 'recursive' => $recursive]; foreach ($dirs as $dir) { $plan[$dir][] = $search; } } } return $plan; } /** * Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal. */ private static function splitRecursivePart(string $path): array { $a = strrpos($path, '/'); $parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2); return isset($parts[1]) ? [$parts[0], $parts[1] . substr($path, $a + 1), true] : [$parts[0], substr($path, $a + 1), false]; } /** * Converts wildcards to regular expression. */ private function buildPattern(string $mask): string { if ($mask === '*') { return '##'; } elseif (str_starts_with($mask, './')) { $anchor = '^'; $mask = substr($mask, 2); } else { $anchor = '(?:^|/)'; } $pattern = strtr( preg_quote($mask, '#'), [ '\*\*/' => '(.+/)?', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-', ], ); return '#' . $anchor . $pattern . '$#D' . (defined('PHP_WINDOWS_VERSION_BUILD') ? 'i' : ''); } } PKZmT% % Utils/ArrayList.phpnu[ $array */ public static function from(array $array): static { if (!Arrays::isList($array)) { throw new Nette\InvalidArgumentException('Array is not valid list.'); } $obj = new static; $obj->list = $array; return $obj; } /** * Returns an iterator over all items. * @return \Iterator */ public function &getIterator(): \Iterator { foreach ($this->list as &$item) { yield $item; } } /** * Returns items count. */ public function count(): int { return count($this->list); } /** * Replaces or appends a item. * @param int|null $index * @param T $value * @throws Nette\OutOfRangeException */ public function offsetSet($index, $value): void { if ($index === null) { $this->list[] = $value; } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) { throw new Nette\OutOfRangeException('Offset invalid or out of range'); } else { $this->list[$index] = $value; } } /** * Returns a item. * @param int $index * @return T * @throws Nette\OutOfRangeException */ public function offsetGet($index): mixed { if (!is_int($index) || $index < 0 || $index >= count($this->list)) { throw new Nette\OutOfRangeException('Offset invalid or out of range'); } return $this->list[$index]; } /** * Determines whether a item exists. * @param int $index */ public function offsetExists($index): bool { return is_int($index) && $index >= 0 && $index < count($this->list); } /** * Removes the element at the specified position in this list. * @param int $index * @throws Nette\OutOfRangeException */ public function offsetUnset($index): void { if (!is_int($index) || $index < 0 || $index >= count($this->list)) { throw new Nette\OutOfRangeException('Offset invalid or out of range'); } array_splice($this->list, $index, 1); } /** * Prepends a item. * @param T $value */ public function prepend(mixed $value): void { $first = array_slice($this->list, 0, 1); $this->offsetSet(0, $value); array_splice($this->list, 1, 0, $first); } } PKZOIddUtils/ObjectHelpers.phpnu[getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), ), $name); throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); } /** * @return never * @throws MemberAccessException */ public static function strictSet(string $class, string $name): void { $rc = new \ReflectionClass($class); $hint = self::getSuggestion(array_merge( array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), ), $name); throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); } /** * @return never * @throws MemberAccessException */ public static function strictCall(string $class, string $method, array $additionalMethods = []): void { $trace = debug_backtrace(0, 3); // suppose this method is called from __call() $context = ($trace[1]['function'] ?? null) === '__call' ? ($trace[2]['class'] ?? null) : null; if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() $class = get_parent_class($context); } if (method_exists($class, $method)) { // insufficient visibility $rm = new \ReflectionMethod($class, $method); $visibility = $rm->isPrivate() ? 'private ' : ($rm->isProtected() ? 'protected ' : ''); throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); } else { $hint = self::getSuggestion(array_merge( get_class_methods($class), self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'), $additionalMethods, ), $method); throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); } } /** * @return never * @throws MemberAccessException */ public static function strictStaticCall(string $class, string $method): void { $trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic() $context = ($trace[1]['function'] ?? null) === '__callStatic' ? ($trace[2]['class'] ?? null) : null; if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() $class = get_parent_class($context); } if (method_exists($class, $method)) { // insufficient visibility $rm = new \ReflectionMethod($class, $method); $visibility = $rm->isPrivate() ? 'private ' : ($rm->isProtected() ? 'protected ' : ''); throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); } else { $hint = self::getSuggestion( array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()), $method, ); throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); } } /** * Returns array of magic properties defined by annotation @property. * @return array of [name => bit mask] * @internal */ public static function getMagicProperties(string $class): array { static $cache; $props = &$cache[$class]; if ($props !== null) { return $props; } $rc = new \ReflectionClass($class); preg_match_all( '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER, ); $props = []; foreach ($matches as [, $type, $name]) { $uname = ucfirst($name); $write = $type !== '-read' && $rc->hasMethod($nm = 'set' . $uname) && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); $read = $type !== '-write' && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); if ($read || $write) { $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4; } } foreach ($rc->getTraits() as $trait) { $props += self::getMagicProperties($trait->name); } if ($parent = get_parent_class($class)) { $props += self::getMagicProperties($parent); } return $props; } /** * Finds the best suggestion (for 8-bit encoding). * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities * @internal */ public static function getSuggestion(array $possibilities, string $value): ?string { $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value); $best = null; $min = (strlen($value) / 4 + 1) * 10 + .1; foreach (array_unique($possibilities, SORT_REGULAR) as $item) { $item = $item instanceof \Reflector ? $item->name : $item; if ($item !== $value && ( ($len = levenshtein($item, $value, 10, 11, 10)) < $min || ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min )) { $min = $len; $best = $item; } } return $best; } private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array { do { $doc[] = $rc->getDocComment(); $traits = $rc->getTraits(); while ($trait = array_pop($traits)) { $doc[] = $trait->getDocComment(); $traits += $trait->getTraits(); } } while ($rc = $rc->getParentClass()); return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : []; } /** * Checks if the public non-static property exists. * Returns 'event' if the property exists and has event like name * @internal */ public static function hasProperty(string $class, string $name): bool|string { static $cache; $prop = &$cache[$class][$name]; if ($prop === null) { $prop = false; try { $rp = new \ReflectionProperty($class, $name); if ($rp->isPublic() && !$rp->isStatic()) { $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; } } catch (\ReflectionException $e) { } } return $prop; } } PKZ,vUUUtils/Strings.phpnu[= 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.'); } elseif (!extension_loaded('iconv')) { throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); } return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); } /** * Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF). */ public static function ord(string $c): int { if (!extension_loaded('iconv')) { throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); } $tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c); if (!$tmp) { throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".'); } return unpack('N', $tmp)[1]; } /** * @deprecated use str_starts_with() */ public static function startsWith(string $haystack, string $needle): bool { return str_starts_with($haystack, $needle); } /** * @deprecated use str_ends_with() */ public static function endsWith(string $haystack, string $needle): bool { return str_ends_with($haystack, $needle); } /** * @deprecated use str_contains() */ public static function contains(string $haystack, string $needle): bool { return str_contains($haystack, $needle); } /** * Returns a part of UTF-8 string specified by starting position and length. If start is negative, * the returned string will start at the start'th character from the end of string. */ public static function substring(string $s, int $start, ?int $length = null): string { if (function_exists('mb_substr')) { return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster } elseif (!extension_loaded('iconv')) { throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.'); } elseif ($length === null) { $length = self::length($s); } elseif ($start < 0 && $length < 0) { $start += self::length($s); // unifies iconv_substr behavior with mb_substr } return iconv_substr($s, $start, $length, 'UTF-8'); } /** * Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines, * trims end spaces on lines, normalizes UTF-8 to the normal form of NFC. */ public static function normalize(string $s): string { // convert to compressed normal form (NFC) if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) { $s = $n; } $s = self::unixNewLines($s); // remove control characters; leave \t + \n $s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]); // right trim $s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]); // leading and trailing blank lines $s = trim($s, "\n"); return $s; } /** @deprecated use Strings::unixNewLines() */ public static function normalizeNewLines(string $s): string { return self::unixNewLines($s); } /** * Converts line endings to \n used on Unix-like systems. * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. */ public static function unixNewLines(string $s): string { return preg_replace("~\r\n?|\u{2028}|\u{2029}~", "\n", $s); } /** * Converts line endings to platform-specific, i.e. \r\n on Windows and \n elsewhere. * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. */ public static function platformNewLines(string $s): string { return preg_replace("~\r\n?|\n|\u{2028}|\u{2029}~", PHP_EOL, $s); } /** * Converts UTF-8 string to ASCII, ie removes diacritics etc. */ public static function toAscii(string $s): string { $iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null; static $transliterator = null; if ($transliterator === null) { if (class_exists('Transliterator', false)) { $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII'); } else { trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE); $transliterator = false; } } // remove control characters and check UTF-8 validity $s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]); // transliteration (by Transliterator and iconv) is not optimal, replace some characters directly $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß if ($iconv !== 'libiconv') { $s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔ } if ($transliterator) { $s = $transliterator->transliterate($s); // use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ if ($iconv === 'glibc') { $s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); $s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters } elseif ($iconv === 'libiconv') { $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); } else { // null or 'unknown' (#216) $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars } } elseif ($iconv === 'glibc' || $iconv === 'libiconv') { // temporarily hide these characters to distinguish them from the garbage that iconv creates $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06"); if ($iconv === 'glibc') { // glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); $s = strtr( $s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.', ); $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); } else { $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); } // remove garbage that iconv creates during transliteration (eg Ý -> Y') $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); // restore temporarily hidden characters $s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); } else { $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars } return $s; } /** * Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters * except letters of the English alphabet and numbers with a hyphens. */ public static function webalize(string $s, ?string $charlist = null, bool $lower = true): string { $s = self::toAscii($s); if ($lower) { $s = strtolower($s); } $s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]); $s = trim($s, '-'); return $s; } /** * Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated, * an ellipsis (or something else set with third argument) is appended to the string. */ public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string { if (self::length($s) > $maxLen) { $maxLen -= self::length($append); if ($maxLen < 1) { return $append; } elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) { return $matches[0] . $append; } else { return self::substring($s, 0, $maxLen) . $append; } } return $s; } /** * Indents a multiline text from the left. Second argument sets how many indentation chars should be used, * while the indent itself is the third argument (*tab* by default). */ public static function indent(string $s, int $level = 1, string $chars = "\t"): string { if ($level > 0) { $s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level)); } return $s; } /** * Converts all characters of UTF-8 string to lower case. */ public static function lower(string $s): string { return mb_strtolower($s, 'UTF-8'); } /** * Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged. */ public static function firstLower(string $s): string { return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1); } /** * Converts all characters of a UTF-8 string to upper case. */ public static function upper(string $s): string { return mb_strtoupper($s, 'UTF-8'); } /** * Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged. */ public static function firstUpper(string $s): string { return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1); } /** * Converts the first character of every word of a UTF-8 string to upper case and the others to lower case. */ public static function capitalize(string $s): string { return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8'); } /** * Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared, * if it is negative, the corresponding number of characters from the end of the strings is compared, * otherwise the appropriate number of characters from the beginning is compared. */ public static function compare(string $left, string $right, ?int $length = null): bool { if (class_exists('Normalizer', false)) { $left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster $right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster } if ($length < 0) { $left = self::substring($left, $length, -$length); $right = self::substring($right, $length, -$length); } elseif ($length !== null) { $left = self::substring($left, 0, $length); $right = self::substring($right, 0, $length); } return self::lower($left) === self::lower($right); } /** * Finds the common prefix of strings or returns empty string if the prefix was not found. * @param string[] $strings */ public static function findPrefix(array $strings): string { $first = array_shift($strings); for ($i = 0; $i < strlen($first); $i++) { foreach ($strings as $s) { if (!isset($s[$i]) || $first[$i] !== $s[$i]) { while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") { $i--; } return substr($first, 0, $i); } } } return $first; } /** * Returns number of characters (not bytes) in UTF-8 string. * That is the number of Unicode code points which may differ from the number of graphemes. */ public static function length(string $s): int { return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s)); } /** * Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string. */ public static function trim(string $s, string $charlist = self::TrimCharacters): string { $charlist = preg_quote($charlist, '#'); return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', ''); } /** * Pads a UTF-8 string to given length by prepending the $pad string to the beginning. */ public static function padLeft(string $s, int $length, string $pad = ' '): string { $length = max(0, $length - self::length($s)); $padLen = self::length($pad); return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; } /** * Pads UTF-8 string to given length by appending the $pad string to the end. */ public static function padRight(string $s, int $length, string $pad = ' '): string { $length = max(0, $length - self::length($s)); $padLen = self::length($pad); return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); } /** * Reverses UTF-8 string. */ public static function reverse(string $s): string { if (!extension_loaded('iconv')) { throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); } return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); } /** * Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found. * Negative value means searching from the end. */ public static function before(string $haystack, string $needle, int $nth = 1): ?string { $pos = self::pos($haystack, $needle, $nth); return $pos === null ? null : substr($haystack, 0, $pos); } /** * Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found. * Negative value means searching from the end. */ public static function after(string $haystack, string $needle, int $nth = 1): ?string { $pos = self::pos($haystack, $needle, $nth); return $pos === null ? null : substr($haystack, $pos + strlen($needle)); } /** * Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found. * Negative value of `$nth` means searching from the end. */ public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int { $pos = self::pos($haystack, $needle, $nth); return $pos === null ? null : self::length(substr($haystack, 0, $pos)); } /** * Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found. */ private static function pos(string $haystack, string $needle, int $nth = 1): ?int { if (!$nth) { return null; } elseif ($nth > 0) { if ($needle === '') { return 0; } $pos = 0; while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) { $pos++; } } else { $len = strlen($haystack); if ($needle === '') { return $len; } elseif ($len === 0) { return null; } $pos = $len - 1; while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) { $pos--; } } return Helpers::falseToNull($pos); } /** * Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well. */ public static function split( string $subject, #[Language('RegExp')] string $pattern, bool|int $captureOffset = false, bool $skipEmpty = false, int $limit = -1, bool $utf8 = false, ): array { $flags = is_int($captureOffset) // back compatibility ? $captureOffset : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); $pattern .= $utf8 ? 'u' : ''; $m = self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); return $utf8 && $captureOffset ? self::bytesToChars($subject, [$m])[0] : $m; } /** * Searches the string for the part matching the regular expression and returns * an array with the found expression and individual subexpressions, or `null`. */ public static function match( string $subject, #[Language('RegExp')] string $pattern, bool|int $captureOffset = false, int $offset = 0, bool $unmatchedAsNull = false, bool $utf8 = false, ): ?array { $flags = is_int($captureOffset) // back compatibility ? $captureOffset : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); if ($utf8) { $offset = strlen(self::substring($subject, 0, $offset)); $pattern .= 'u'; } if ($offset > strlen($subject)) { return null; } elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) { return null; } elseif ($utf8 && $captureOffset) { return self::bytesToChars($subject, [$m])[0]; } else { return $m; } } /** * Searches the string for all occurrences matching the regular expression and * returns an array of arrays containing the found expression and each subexpression. */ public static function matchAll( string $subject, #[Language('RegExp')] string $pattern, bool|int $captureOffset = false, int $offset = 0, bool $unmatchedAsNull = false, bool $patternOrder = false, bool $utf8 = false, ): array { $flags = is_int($captureOffset) // back compatibility ? $captureOffset : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0); if ($utf8) { $offset = strlen(self::substring($subject, 0, $offset)); $pattern .= 'u'; } if ($offset > strlen($subject)) { return []; } self::pcre('preg_match_all', [ $pattern, $subject, &$m, ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), $offset, ]); return $utf8 && $captureOffset ? self::bytesToChars($subject, $m) : $m; } /** * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`. */ public static function replace( string $subject, #[Language('RegExp')] string|array $pattern, string|callable $replacement = '', int $limit = -1, bool $captureOffset = false, bool $unmatchedAsNull = false, bool $utf8 = false, ): string { if (is_object($replacement) || is_array($replacement)) { if (!is_callable($replacement, false, $textual)) { throw new Nette\InvalidStateException("Callback '$textual' is not callable."); } $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); if ($utf8) { $pattern .= 'u'; if ($captureOffset) { $replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]); } } return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]); } elseif (is_array($pattern) && is_string(key($pattern))) { $replacement = array_values($pattern); $pattern = array_keys($pattern); } if ($utf8) { $pattern = array_map(fn($item) => $item . 'u', (array) $pattern); } return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); } private static function bytesToChars(string $s, array $groups): array { $lastBytes = $lastChars = 0; foreach ($groups as &$matches) { foreach ($matches as &$match) { if ($match[1] > $lastBytes) { $lastChars += self::length(substr($s, $lastBytes, $match[1] - $lastBytes)); } elseif ($match[1] < $lastBytes) { $lastChars -= self::length(substr($s, $match[1], $lastBytes - $match[1])); } $lastBytes = $match[1]; $match[1] = $lastChars; } } return $groups; } /** @internal */ public static function pcre(string $func, array $args) { $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void { // compile-time error, not detectable by preg_last_error throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); }); if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true)) ) { throw new RegexpException(preg_last_error_msg() . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); } return $res; } } PKZ_  Utils/FileInfo.phpnu[setInfoClass(static::class); $this->relativePath = $relativePath; } /** * Returns the relative directory path. */ public function getRelativePath(): string { return $this->relativePath; } /** * Returns the relative path including file name. */ public function getRelativePathname(): string { return ($this->relativePath === '' ? '' : $this->relativePath . DIRECTORY_SEPARATOR) . $this->getBasename(); } /** * Returns the contents of the file. * @throws Nette\IOException */ public function read(): string { return FileSystem::read($this->getPathname()); } /** * Writes the contents to the file. * @throws Nette\IOException */ public function write(string $content): void { FileSystem::write($this->getPathname(), $content); } } PKZ%X$X$Utils/FileSystem.phpnu[getPathname()); } foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { if ($item->isDir()) { static::createDir($target . '/' . $iterator->getSubPathName()); } else { static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName()); } } } else { static::createDir(dirname($target)); if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to copy file '%s' to '%s'. %s", self::normalizePath($origin), self::normalizePath($target), Helpers::getLastError(), )); } } } /** * Opens file and returns resource. * @return resource * @throws Nette\IOException on error occurred */ public static function open(string $path, string $mode) { $f = @fopen($path, $mode); // @ is escalated to exception if (!$f) { throw new Nette\IOException(sprintf( "Unable to open file '%s'. %s", self::normalizePath($path), Helpers::getLastError(), )); } return $f; } /** * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first. * @throws Nette\IOException on error occurred */ public static function delete(string $path): void { if (is_file($path) || is_link($path)) { $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink'; if (!@$func($path)) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to delete '%s'. %s", self::normalizePath($path), Helpers::getLastError(), )); } } elseif (is_dir($path)) { foreach (new \FilesystemIterator($path) as $item) { static::delete($item->getPathname()); } if (!@rmdir($path)) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to delete directory '%s'. %s", self::normalizePath($path), Helpers::getLastError(), )); } } } /** * Renames or moves a file or a directory. Overwrites existing files and directories by default. * @throws Nette\IOException on error occurred * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists */ public static function rename(string $origin, string $target, bool $overwrite = true): void { if (!$overwrite && file_exists($target)) { throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target))); } elseif (!file_exists($origin)) { throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin))); } else { static::createDir(dirname($target)); if (realpath($origin) !== realpath($target)) { static::delete($target); } if (!@rename($origin, $target)) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to rename file or directory '%s' to '%s'. %s", self::normalizePath($origin), self::normalizePath($target), Helpers::getLastError(), )); } } } /** * Reads the content of a file. * @throws Nette\IOException on error occurred */ public static function read(string $file): string { $content = @file_get_contents($file); // @ is escalated to exception if ($content === false) { throw new Nette\IOException(sprintf( "Unable to read file '%s'. %s", self::normalizePath($file), Helpers::getLastError(), )); } return $content; } /** * Reads the file content line by line. Because it reads continuously as we iterate over the lines, * it is possible to read files larger than the available memory. * @return \Generator * @throws Nette\IOException on error occurred */ public static function readLines(string $file, bool $stripNewLines = true): \Generator { return (function ($f) use ($file, $stripNewLines) { $counter = 0; do { $line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf( "Unable to read file '%s'. %s", self::normalizePath($file), $error, ))); if ($line === false) { fclose($f); break; } if ($stripNewLines) { $line = rtrim($line, "\r\n"); } yield $counter++ => $line; } while (true); })(static::open($file, 'r')); } /** * Writes the string to a file. * @throws Nette\IOException on error occurred */ public static function write(string $file, string $content, ?int $mode = 0666): void { static::createDir(dirname($file)); if (@file_put_contents($file, $content) === false) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to write file '%s'. %s", self::normalizePath($file), Helpers::getLastError(), )); } if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to chmod file '%s' to mode %s. %s", self::normalizePath($file), decoct($mode), Helpers::getLastError(), )); } } /** * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`. * Recursively traverses and sets permissions on the entire contents of the directory as well. * @throws Nette\IOException on error occurred */ public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void { if (is_file($path)) { if (!@chmod($path, $fileMode)) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to chmod file '%s' to mode %s. %s", self::normalizePath($path), decoct($fileMode), Helpers::getLastError(), )); } } elseif (is_dir($path)) { foreach (new \FilesystemIterator($path) as $item) { static::makeWritable($item->getPathname(), $dirMode, $fileMode); } if (!@chmod($path, $dirMode)) { // @ is escalated to exception throw new Nette\IOException(sprintf( "Unable to chmod directory '%s' to mode %s. %s", self::normalizePath($path), decoct($dirMode), Helpers::getLastError(), )); } } else { throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path))); } } /** * Determines if the path is absolute. */ public static function isAbsolute(string $path): bool { return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path); } /** * Normalizes `..` and `.` and directory separators in path. */ public static function normalizePath(string $path): string { $parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path); $res = []; foreach ($parts as $part) { if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') { array_pop($res); } elseif ($part !== '.') { $res[] = $part; } } return $res === [''] ? DIRECTORY_SEPARATOR : implode(DIRECTORY_SEPARATOR, $res); } /** * Joins all segments of the path and normalizes the result. */ public static function joinPaths(string ...$paths): string { return self::normalizePath(implode('/', $paths)); } /** * Converts backslashes to slashes. */ public static function unixSlashes(string $path): string { return strtr($path, '\\', '/'); } /** * Converts slashes to platform-specific directory separators. */ public static function platformSlashes(string $path): string { return DIRECTORY_SEPARATOR === '/' ? strtr($path, '\\', '/') : str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol:// } } PKZ[ KKUtils/Random.phpnu[ implode('', range($m[0][0], $m[0][2])), $charlist, ); $charlist = count_chars($charlist, mode: 3); $chLen = strlen($charlist); if ($length < 1) { throw new Nette\InvalidArgumentException('Length must be greater than zero.'); } elseif ($chLen < 2) { throw new Nette\InvalidArgumentException('Character list must contain at least two chars.'); } $res = ''; for ($i = 0; $i < $length; $i++) { $res .= $charlist[random_int(0, $chLen - 1)]; } return $res; } } PKZe Utils/Callback.phpnu[name, '}')) { return $closure; } elseif ($obj = $r->getClosureThis()) { return [$obj, $r->name]; } elseif ($class = $r->getClosureScopeClass()) { return [$class->name, $r->name]; } else { return $r->name; } } } PKZ6OOHtmlStringable.phpnu['Whoops/Inspector/InspectorInterface.phpnu[PKcRZe5++.uWhoops/Inspector/InspectorFactoryInterface.phpnu[PKcRZeY%Whoops/Inspector/InspectorFactory.phpnu[PKcRZ Whoops/Util/Misc.phpnu[PKcRZJ Whoops/Util/SystemFacade.phpnu[PKcRZZ Whoops/Util/HtmlDumperOutput.phpnu[PKcRZM%%Whoops/Util/TemplateHelper.phpnu[PKcRZ ))$DEWhoops/Exception/FrameCollection.phpnu[PKcRZ.%%YWhoops/Exception/Inspector.phpnu[PKcRZ"sCcc#Whoops/Exception/ErrorException.phpnu[PKcRZ9Un Whoops/Exception/Formatter.phpnu[PKcRZŢWhoops/Exception/Frame.phpnu[PKcRZkZ==&ūWhoops/Handler/JsonResponseHandler.phpnu[PKcRZBU$HH"XWhoops/Handler/CallbackHandler.phpnu[PKcRZU # ##Whoops/Handler/PlainTextHandler.phpnu[PKcRZԷ#PWhoops/Handler/HandlerInterface.phpnu[PKcRZzzWhoops/Handler/Handler.phpnu[PKcRZ A %QWhoops/Handler/XmlResponseHandler.phpnu[PKcRZ*ܤdd$IWhoops/Handler/PrettyPageHandler.phpnu[PKcRZىAXWhoops/RunInterface.phpnu[PKcRZ$ȁ4 JhWhoops/Resources/css/prism.cssnu[PKcRZ5(5($"uWhoops/Resources/css/whoops.base.cssnu[PKcRZ2Whoops/Resources/views/frames_description.html.phpnu[PKcRZ޲!mm&Whoops/Resources/views/layout.html.phpnu[PKcRZ\ FF-dWhoops/Resources/views/panel_details.html.phpnu[PKcRZ-_+Whoops/Resources/views/env_details.html.phpnu[PKcRZYyy0{Whoops/Resources/views/panel_left_outer.html.phpnu[PKcRZGw"WW3TWhoops/Resources/views/panel_details_outer.html.phpnu[PKcRZ9 9 *Whoops/Resources/views/frame_code.html.phpnu[PKcRZ*Whoops/Resources/views/frame_list.html.phpnu[PKcRZ"hh* Whoops/Resources/views/panel_left.html.phpnu[PKcRZ.|w44,̽Whoops/Resources/views/header_outer.html.phpnu[PKcRZ W0\Whoops/Resources/views/frames_container.html.phpnu[PKcRZkg%$$&^Whoops/Resources/views/header.html.phpnu[PKcRZ>^>^}Whoops/Resources/js/prism.jsnu[PKcRZ/0<`<` CWhoops/Resources/js/zepto.min.jsnu[PKcRZX2foo"Whoops/Resources/js/whoops.base.jsnu[PKcRZL"L"$TWhoops/Resources/js/clipboard.min.jsnu[PKcRZ6y+"??Whoops/Run.phpnu[PKZ wwLoggerTrait.phpnu[PKZK;+NullLogger.phpnu[PKZ~//.LoggerAwareInterface.phpnu[PKZPP '0LogLevel.phpnu[PKZ X1``1InvalidArgumentException.phpnu[PKZAaHA A _2LoggerInterface.phpnu[PKZۛ?AbstractLogger.phpnu[PKZ>ALoggerAwareTrait.phpnu[PK ]Zs≱CConsole/InstallCommand.phpnu[PK ]Z.^JConsole/AddCommand.phpnu[PK ]Z^PConsole/PublishCommand.phpnu[PK ]ZU##7UConsole/Concerns/InteractsWithDockerComposeServices.phpnu[PK ]ZxsaXXySailServiceProvider.phpnu[PKZiSet.phpnu[PKZ)AbstractArray.phpnu[PKZKDCollectionInterface.phpnu[PKZɢ; Queue.phpnu[PKZ1Tool/TypeTrait.phpnu[PKZ@W W Tool/ValueToStringTrait.phpnu[PKZX'Tool/ValueExtractorTrait.phpnu[PKZ)^ K))DoubleEndedQueueInterface.phpnu[PKZN$ Exception/NoSuchElementException.phpnu[PKZU"$  + Exception/UnsupportedOperationException.phpnu[PKZGF  ):Exception/CollectionMismatchException.phpnu[PKZ- "Exception/OutOfBoundsException.phpnu[PKZ C~'Exception/InvalidSortOrderException.phpnu[PKZ|f&aException/ValueExtractionException.phpnu[PKZ:&Exception/InvalidArgumentException.phpnu[PKZ@ lMM/AbstractSet.phpnu[PKZ;I( ( Collection.phpnu[PKZh!*DoubleEndedQueue.phpnu[PKZ㸿 9Map/AbstractMap.phpnu[PKZ:(  EMap/NamedParameterMap.phpnu[PKZ =T%%;SMap/AssociativeArrayMap.phpnu[PKZ`!  UMap/MapInterface.phpnu[PKZdʼn jMap/TypedMap.phpnu[PKZRwMap/AbstractTypedMap.phpnu[PKZMap/TypedMapInterface.phpnu[PKZVV@QueueInterface.phpnu[PKZIIؠArrayInterface.phpnu[PKZKScGenericArray.phpnu[PKZF~M#M#AbstractCollection.phpnu[PKZ9666DegradedUuid.phpnu[PKZ|l7?55DeprecatedUuidMethodsTrait.phpnu[PKZ݁zBinaryUtils.phpnu[PKZC  Validator/ValidatorInterface.phpnu[PKZm4t:jjValidator/GenericValidator.phpnu[PKZfEELazy/LazyUuidFromString.phpnu[PKZ_~vvZRfc4122/UuidV2.phpnu[PKZ>>kRfc4122/MaxUuid.phpnu[PKZ  nRfc4122/UuidV6.phpnu[PKZ;;PqRfc4122/UuidV7.phpnu[PKZbnyRfc4122/MaxTrait.phpnu[PKZf[$~Rfc4122/UuidV1.phpnu[PKZ}u:Rfc4122/UuidV3.phpnu[PKZf>PRfc4122/UuidBuilder.phpnu[PKZzrRfc4122/UuidV5.phpnu[PKZ/Rfc4122/TimeTrait.phpnu[PKZ*_Rfc4122/Validator.phpnu[PKZ ;Rfc4122/VersionTrait.phpnu[PKZ'rRfc4122/NilTrait.phpnu[PKZ.% % yRfc4122/UuidV8.phpnu[PKZZ##Rfc4122/FieldsInterface.phpnu[PKZJJNRfc4122/Fields.phpnu[PKZRfc4122/UuidV4.phpnu[PKZcM??Rfc4122/NilUuid.phpnu[PKZw="Rfc4122/UuidInterface.phpnu[PKZ> x x Rfc4122/VariantTrait.phpnu[PKZ00FeatureSet.phpnu[PKZxx x 7Type/Time.phpnu[PKZ{{EType/NumberInterface.phpnu[PKZTD,\ SHType/Hexadecimal.phpnu[PKZ4+ + .SType/Decimal.phpnu[PKZ{ޕ_Type/TypeInterface.phpnu[PKZ_~P##tbType/Integer.phpnu[PKZ < < qNonstandard/UuidV6.phpnu[PKZ YNonstandard/UuidBuilder.phpnu[PKZ>B #bNonstandard/Uuid.phpnu[PKZ!pD/ / aNonstandard/Fields.phpnu[PKZ=DD!֙Exception/TimeSourceException.phpnu[PKZTTkException/DateTimeException.phpnu[PKZ3tN#Exception/RandomSourceException.phpnu[PKZo cc"SException/DceSecurityException.phpnu[PKZveDD&Exception/BuilderNotFoundException.phpnu[PKZN(Exception/InvalidUuidStringException.phpnu[PKZR$Exception/UuidExceptionInterface.phpnu[PKZouRR#Exception/InvalidBytesException.phpnu[PKZYY`Exception/NameException.phpnu[PKZ. SSException/NodeException.phpnu[PKZH%_BB(Exception/UnableToBuildUuidException.phpnu[PKZ#<<UuidFactoryInterface.phpnu[PKZGU~~jMath/CalculatorInterface.phpnu[PKZ1}l4Math/BrickMathCalculator.phpnu[PKZZjeMath/RoundingMode.phpnu[PKZ/ Generator/CombGenerator.phpnu[PKZ"< Generator/TimeGeneratorFactory.phpnu[PKZ!"@ Generator/NameGeneratorFactory.phpnu[PKZQJC Generator/RandomLibAdapter.phpnu[PKZii#OJ Generator/PeclUuidTimeGenerator.phpnu[PKZBx$ N Generator/TimeGeneratorInterface.phpnu[PKZJ$R Generator/RandomGeneratorFactory.phpnu[PKZ'&xx"U Generator/RandomBytesGenerator.phpnu[PKZ m$&Z Generator/RandomGeneratorInterface.phpnu[PKZN׈QQ] Generator/UnixTimeGenerator.phpnu[PKZs**$u Generator/NameGeneratorInterface.phpnu[PKZh33"z Generator/DefaultTimeGenerator.phpnu[PKZ(``" Generator/DceSecurityGenerator.phpnu[PKZ Q UuidInterface.phpnu[PKZ滭r55( Internal/Calculator/NativeCalculator.phpnu[PKZmK~L  % Internal/Calculator/GmpCalculator.phpnu[PKZ[H( Internal/Calculator/BcMathCalculator.phpnu[PKZLKK[ Internal/Calculator.phpnu[PKZ nEWEWS9 BigDecimal.phpnu[PKZ<< ֐ BigNumber.phpnu[PKZE 2 2 BigRational.phpnu[PKZV~&P Exception/IntegerOverflowException.phpnu[PKZ1>( Exception/RoundingNecessaryException.phpnu[PKZY% Exception/NegativeNumberException.phpnu[PKZ*؜$ Exception/MathException.phpnu[PKZ^|"#  Exception/NumberFormatException.phpnu[PKZəb%Q Exception/DivisionByZeroException.phpnu[PKZ\ BigInteger.phpnu[PKZSŐ RoundingMode.phpnu[PKZ,YY Iterators/Mapper.phpnu[PKZ2  Iterators/CachingIterator.phpnu[PKZA Translator.phpnu[PKZް exceptions.phpnu[PKZwX  SmartObject.phpnu[PKZrk compatibility.phpnu[PKZlyy StaticClass.phpnu[PKZfHH Utils/Floats.phpnu[PKZUUD Utils/Image.phpnu[PKZO!*!*+ Utils/Arrays.phpnu[PKZ8U Utils/Type.phpnu[PKZsvW(W(o Utils/Validators.phpnu[PKZd Utils/exceptions.phpnu[PKZں Utils/ArrayHash.phpnu[PKZ I Utils/DateTime.phpnu[PKZ9  Utils/Helpers.phpnu[PKZsBLL` Utils/Html.phpnu[PKZzd!d! Utils/Reflection.phpnu[PKZ4+^& Utils/Json.phpnu[PKZ0;,,~/ Utils/Paginator.phpnu[PKZf-A3A3? Utils/Finder.phpnu[PKZmT% % ns Utils/ArrayList.phpnu[PKZOIdd} Utils/ObjectHelpers.phpnu[PKZ,vUU Utils/Strings.phpnu[PKZ_   Utils/FileInfo.phpnu[PKZ%X$X$ Utils/FileSystem.phpnu[PKZ[ KKxUtils/Random.phpnu[PKZe Utils/Callback.phpnu[PKZ6OO,HtmlStringable.phpnu[PKHT-